init commit

This commit is contained in:
2025-10-19 18:27:00 +09:00
commit 150891b29d
219 changed files with 70016 additions and 0 deletions

34
.env.example Normal file
View File

@@ -0,0 +1,34 @@
# Environment Configuration
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# JWT Secret
JWT_SECRET=your_super_secret_jwt_key_here_change_in_production
# Session Secret
SESSION_SECRET=your_session_secret_here_change_in_production
# Telegram Bot
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=your_telegram_chat_id_here
# Email Configuration (for contact forms)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_email_password
# Admin Credentials (default)
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=admin123456
# File Upload
MAX_FILE_SIZE=10485760
UPLOAD_PATH=./public/uploads
# Site Configuration
SITE_URL=https://smartsoltech.kr
SITE_NAME=SmartSolTech

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
node_modules/
.env
.env.local
.env.production
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
dist/
build/
uploads/
*.log
.vscode/
.idea/
coverage/
.nyc_output/
*.tgz
*.tar.gz
.cache/
.parcel-cache/

View File

@@ -0,0 +1,34 @@
# Environment Configuration
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# JWT Secret
JWT_SECRET=your_super_secret_jwt_key_here_change_in_production
# Session Secret
SESSION_SECRET=your_session_secret_here_change_in_production
# Telegram Bot
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=your_telegram_chat_id_here
# Email Configuration (for contact forms)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_email_password
# Admin Credentials (default)
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=admin123456
# File Upload
MAX_FILE_SIZE=10485760
UPLOAD_PATH=./public/uploads
# Site Configuration
SITE_URL=https://smartsoltech.kr
SITE_NAME=SmartSolTech

View File

@@ -0,0 +1,34 @@
# Environment Configuration
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# JWT Secret
JWT_SECRET=your_super_secret_jwt_key_here_change_in_production
# Session Secret
SESSION_SECRET=your_session_secret_here_change_in_production
# Telegram Bot
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=your_telegram_chat_id_here
# Email Configuration (for contact forms)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_email_password
# Admin Credentials (default)
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=admin123456
# File Upload
MAX_FILE_SIZE=10485760
UPLOAD_PATH=./public/uploads
# Site Configuration
SITE_URL=https://smartsoltech.kr
SITE_NAME=SmartSolTech

View File

@@ -0,0 +1,20 @@
node_modules/
.env
.env.local
.env.production
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
dist/
build/
uploads/
*.log
.vscode/
.idea/
coverage/
.nyc_output/
*.tgz
*.tar.gz
.cache/
.parcel-cache/

View File

@@ -0,0 +1,20 @@
node_modules/
.env
.env.local
.env.production
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
dist/
build/
uploads/
*.log
.vscode/
.idea/
coverage/
.nyc_output/
*.tgz
*.tar.gz
.cache/
.parcel-cache/

View File

@@ -0,0 +1,280 @@
# SmartSolTech Website
Современный PWA сайт для SmartSolTech с админ-панелью, управлением портфолио, интеграцией с Telegram ботом и калькулятором стоимости услуг.
## 🚀 Особенности
- **Современный дизайн**: Отзывчивый интерфейс с анимациями и современным UI/UX
- **PWA**: Прогрессивное веб-приложение с поддержкой офлайн-режима
- **Админ-панель**: Управление портфолио, услугами, медиа-контентом и настройками сайта
- **Интеграция с Telegram**: Подключение к Telegram боту для уведомлений
- **Калькулятор стоимости**: Интерактивный калькулятор расчета стоимости услуг
- **Система аутентификации**: Безопасная авторизация с JWT токенами
- **Загрузка файлов**: Оптимизация изображений и управление медиа-контентом
- **SEO оптимизация**: Настроенные мета-теги и структурированные данные
## 🛠️ Технологии
### Backend
- **Node.js** - Серверная платформа
- **Express.js** - Веб-фреймворк
- **MongoDB** - База данных
- **Mongoose** - ODM для MongoDB
- **JWT** - Токены аутентификации
- **bcrypt** - Хеширование паролей
- **Multer** - Загрузка файлов
- **Sharp** - Обработка изображений
### Frontend
- **EJS** - Шаблонизатор
- **Tailwind CSS** - CSS фреймворк
- **AOS** - Библиотека анимаций
- **Font Awesome** - Иконки
- **Service Worker** - PWA функциональность
### Дополнительно
- **node-telegram-bot-api** - Интеграция с Telegram
- **Nodemailer** - Отправка email
- **Helmet** - Безопасность
- **express-rate-limit** - Ограничение запросов
## 📦 Установка
1. **Клонирование репозитория**
```bash
git clone <repository-url>
cd sst_site
```
2. **Установка зависимостей**
```bash
npm install
```
3. **Настройка переменных окружения**
```bash
cp .env.example .env
```
Отредактируйте файл `.env` с вашими настройками:
```env
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# Security
SESSION_SECRET=your-super-secret-session-key
JWT_SECRET=your-super-secret-jwt-key
# File Upload
UPLOAD_PATH=./uploads
MAX_FILE_SIZE=10485760
# Email Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
# Telegram Bot (Optional)
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
# Admin Account
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=change-this-password
```
4. **Инициализация базы данных**
```bash
npm run init-db
```
5. **Запуск в режиме разработки**
```bash
npm run dev
```
Сайт будет доступен по адресу: `http://localhost:3000`
## 🗂️ Структура проекта
```
sst_site/
├── models/ # Модели данных (MongoDB)
│ ├── User.js
│ ├── Portfolio.js
│ ├── Service.js
│ ├── Contact.js
│ └── SiteSettings.js
├── routes/ # Маршруты API
│ ├── index.js
│ ├── auth.js
│ ├── contact.js
│ ├── calculator.js
│ ├── portfolio.js
│ ├── services.js
│ ├── media.js
│ └── admin.js
├── views/ # Шаблоны EJS
│ ├── layout.ejs
│ ├── index.ejs
│ ├── calculator.ejs
│ └── partials/
├── public/ # Статические файлы
│ ├── css/
│ ├── js/
│ ├── images/
│ ├── manifest.json
│ └── sw.js
├── middleware/ # Промежуточное ПО
├── scripts/ # Служебные скрипты
│ ├── init-db.js
│ ├── dev.js
│ └── build.js
├── uploads/ # Загруженные файлы
├── server.js # Главный файл сервера
├── package.json
└── README.md
```
## 📋 Доступные команды
```bash
# Разработка
npm run dev # Запуск в режиме разработки с hot reload
# Продакшн
npm start # Запуск в продакшн режиме
npm run build # Сборка для продакшна
# База данных
npm run init-db # Инициализация БД с тестовыми данными
# Тестирование
npm test # Запуск тестов (пока не реализовано)
```
## 🔐 Админ-панель
После инициализации базы данных вы можете войти в админ-панель:
- **URL**: `http://localhost:3000/admin`
- **Email**: `admin@smartsoltech.kr` (или из .env)
- **Пароль**: Указанный в .env файле
### Возможности админ-панели:
- Управление портфолио проектами
- Редактирование услуг и их стоимости
- Загрузка и управление медиа-файлами
- Просмотр и управление контактными формами
- Настройки сайта и SEO
- Управление пользователями
## 🤖 Интеграция с Telegram
Для настройки Telegram бота:
1. Создайте бота через [@BotFather](https://t.me/BotFather)
2. Получите токен бота
3. Добавьте токен в `.env` файл: `TELEGRAM_BOT_TOKEN=your-token`
4. Перезапустите сервер
Бот будет отправлять уведомления о:
- Новых контактных формах
- Заказах через калькулятор
- Новых комментариях
## 💰 Калькулятор стоимости
Интерактивный калькулятор позволяет клиентам:
- Выбрать тип услуги
- Указать дополнительные параметры
- Получить примерную стоимость
- Отправить заявку на расчет
Настройки калькулятора можно изменить в админ-панели.
## 🔒 Безопасность
Проект включает следующие меры безопасности:
- Хеширование паролей с bcrypt
- JWT токены для аутентификации
- Защита от CSRF атак
- Ограничение количества запросов
- Валидация входных данных
- Безопасные HTTP заголовки
## 📱 PWA функции
- Установка на устройство
- Офлайн работа
- Push уведомления
- Фоновая синхронизация
- Адаптивные иконки
- Splash screen
## 🚀 Деплой
### Для продакшна:
1. **Сборка приложения**
```bash
npm run build
```
2. **Настройка сервера**
- Установите Node.js и MongoDB
- Настройте переменные окружения
- Настройте прокси-сервер (nginx)
3. **Запуск**
```bash
cd dist
npm install --production
npm start
```
### Docker деплой:
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
```
## 🤝 Участие в разработке
1. Форкните репозиторий
2. Создайте ветку для фичи (`git checkout -b feature/AmazingFeature`)
3. Сделайте коммит (`git commit -m 'Add some AmazingFeature'`)
4. Отправьте в ветку (`git push origin feature/AmazingFeature`)
5. Откройте Pull Request
## 📝 Лицензия
Этот проект лицензирован под MIT License - подробности в файле [LICENSE](LICENSE).
## 📞 Поддержка
Если у вас есть вопросы или проблемы:
- Email: info@smartsoltech.kr
- GitHub Issues: [Create Issue](../../issues)
- Telegram: @smartsoltech
## 🙏 Благодарности
- [Express.js](https://expressjs.com/) - За отличный веб-фреймворк
- [MongoDB](https://www.mongodb.com/) - За гибкую базу данных
- [Tailwind CSS](https://tailwindcss.com/) - За удобный CSS фреймворк
- [AOS](https://michalsnik.github.io/aos/) - За красивые анимации
---
**SmartSolTech** - Умные решения для вашего бизнеса 🚀

View File

@@ -0,0 +1,280 @@
# SmartSolTech Website
Современный PWA сайт для SmartSolTech с админ-панелью, управлением портфолио, интеграцией с Telegram ботом и калькулятором стоимости услуг.
## 🚀 Особенности
- **Современный дизайн**: Отзывчивый интерфейс с анимациями и современным UI/UX
- **PWA**: Прогрессивное веб-приложение с поддержкой офлайн-режима
- **Админ-панель**: Управление портфолио, услугами, медиа-контентом и настройками сайта
- **Интеграция с Telegram**: Подключение к Telegram боту для уведомлений
- **Калькулятор стоимости**: Интерактивный калькулятор расчета стоимости услуг
- **Система аутентификации**: Безопасная авторизация с JWT токенами
- **Загрузка файлов**: Оптимизация изображений и управление медиа-контентом
- **SEO оптимизация**: Настроенные мета-теги и структурированные данные
## 🛠️ Технологии
### Backend
- **Node.js** - Серверная платформа
- **Express.js** - Веб-фреймворк
- **MongoDB** - База данных
- **Mongoose** - ODM для MongoDB
- **JWT** - Токены аутентификации
- **bcrypt** - Хеширование паролей
- **Multer** - Загрузка файлов
- **Sharp** - Обработка изображений
### Frontend
- **EJS** - Шаблонизатор
- **Tailwind CSS** - CSS фреймворк
- **AOS** - Библиотека анимаций
- **Font Awesome** - Иконки
- **Service Worker** - PWA функциональность
### Дополнительно
- **node-telegram-bot-api** - Интеграция с Telegram
- **Nodemailer** - Отправка email
- **Helmet** - Безопасность
- **express-rate-limit** - Ограничение запросов
## 📦 Установка
1. **Клонирование репозитория**
```bash
git clone <repository-url>
cd sst_site
```
2. **Установка зависимостей**
```bash
npm install
```
3. **Настройка переменных окружения**
```bash
cp .env.example .env
```
Отредактируйте файл `.env` с вашими настройками:
```env
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# Security
SESSION_SECRET=your-super-secret-session-key
JWT_SECRET=your-super-secret-jwt-key
# File Upload
UPLOAD_PATH=./uploads
MAX_FILE_SIZE=10485760
# Email Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
# Telegram Bot (Optional)
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
# Admin Account
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=change-this-password
```
4. **Инициализация базы данных**
```bash
npm run init-db
```
5. **Запуск в режиме разработки**
```bash
npm run dev
```
Сайт будет доступен по адресу: `http://localhost:3000`
## 🗂️ Структура проекта
```
sst_site/
├── models/ # Модели данных (MongoDB)
│ ├── User.js
│ ├── Portfolio.js
│ ├── Service.js
│ ├── Contact.js
│ └── SiteSettings.js
├── routes/ # Маршруты API
│ ├── index.js
│ ├── auth.js
│ ├── contact.js
│ ├── calculator.js
│ ├── portfolio.js
│ ├── services.js
│ ├── media.js
│ └── admin.js
├── views/ # Шаблоны EJS
│ ├── layout.ejs
│ ├── index.ejs
│ ├── calculator.ejs
│ └── partials/
├── public/ # Статические файлы
│ ├── css/
│ ├── js/
│ ├── images/
│ ├── manifest.json
│ └── sw.js
├── middleware/ # Промежуточное ПО
├── scripts/ # Служебные скрипты
│ ├── init-db.js
│ ├── dev.js
│ └── build.js
├── uploads/ # Загруженные файлы
├── server.js # Главный файл сервера
├── package.json
└── README.md
```
## 📋 Доступные команды
```bash
# Разработка
npm run dev # Запуск в режиме разработки с hot reload
# Продакшн
npm start # Запуск в продакшн режиме
npm run build # Сборка для продакшна
# База данных
npm run init-db # Инициализация БД с тестовыми данными
# Тестирование
npm test # Запуск тестов (пока не реализовано)
```
## 🔐 Админ-панель
После инициализации базы данных вы можете войти в админ-панель:
- **URL**: `http://localhost:3000/admin`
- **Email**: `admin@smartsoltech.kr` (или из .env)
- **Пароль**: Указанный в .env файле
### Возможности админ-панели:
- Управление портфолио проектами
- Редактирование услуг и их стоимости
- Загрузка и управление медиа-файлами
- Просмотр и управление контактными формами
- Настройки сайта и SEO
- Управление пользователями
## 🤖 Интеграция с Telegram
Для настройки Telegram бота:
1. Создайте бота через [@BotFather](https://t.me/BotFather)
2. Получите токен бота
3. Добавьте токен в `.env` файл: `TELEGRAM_BOT_TOKEN=your-token`
4. Перезапустите сервер
Бот будет отправлять уведомления о:
- Новых контактных формах
- Заказах через калькулятор
- Новых комментариях
## 💰 Калькулятор стоимости
Интерактивный калькулятор позволяет клиентам:
- Выбрать тип услуги
- Указать дополнительные параметры
- Получить примерную стоимость
- Отправить заявку на расчет
Настройки калькулятора можно изменить в админ-панели.
## 🔒 Безопасность
Проект включает следующие меры безопасности:
- Хеширование паролей с bcrypt
- JWT токены для аутентификации
- Защита от CSRF атак
- Ограничение количества запросов
- Валидация входных данных
- Безопасные HTTP заголовки
## 📱 PWA функции
- Установка на устройство
- Офлайн работа
- Push уведомления
- Фоновая синхронизация
- Адаптивные иконки
- Splash screen
## 🚀 Деплой
### Для продакшна:
1. **Сборка приложения**
```bash
npm run build
```
2. **Настройка сервера**
- Установите Node.js и MongoDB
- Настройте переменные окружения
- Настройте прокси-сервер (nginx)
3. **Запуск**
```bash
cd dist
npm install --production
npm start
```
### Docker деплой:
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
```
## 🤝 Участие в разработке
1. Форкните репозиторий
2. Создайте ветку для фичи (`git checkout -b feature/AmazingFeature`)
3. Сделайте коммит (`git commit -m 'Add some AmazingFeature'`)
4. Отправьте в ветку (`git push origin feature/AmazingFeature`)
5. Откройте Pull Request
## 📝 Лицензия
Этот проект лицензирован под MIT License - подробности в файле [LICENSE](LICENSE).
## 📞 Поддержка
Если у вас есть вопросы или проблемы:
- Email: info@smartsoltech.kr
- GitHub Issues: [Create Issue](../../issues)
- Telegram: @smartsoltech
## 🙏 Благодарности
- [Express.js](https://expressjs.com/) - За отличный веб-фреймворк
- [MongoDB](https://www.mongodb.com/) - За гибкую базу данных
- [Tailwind CSS](https://tailwindcss.com/) - За удобный CSS фреймворк
- [AOS](https://michalsnik.github.io/aos/) - За красивые анимации
---
**SmartSolTech** - Умные решения для вашего бизнеса 🚀

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "Home",
"about": "About",
"services": "Services",
"portfolio": "Portfolio",
"contact": "Contact",
"calculator": "Calculator",
"admin": "Admin"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation",
"cta_primary": "Start Project",
"cta_secondary": "View Portfolio"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "Digital solutions completed with cutting-edge technology and creative ideas",
"web_development": {
"title": "Web Development",
"description": "Modern and responsive websites and web applications development",
"price": "$5,000~"
},
"mobile_app": {
"title": "Mobile App",
"description": "Native and cross-platform apps for iOS and Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX Design",
"description": "User-centered intuitive and beautiful interface design",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Digital Marketing",
"description": "Digital marketing through SEO, social media, online advertising",
"price": "$2,000~"
},
"view_all": "View All Services"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "Check out the projects completed for customer success",
"view_details": "View Details",
"view_all": "View All Portfolio"
},
"calculator": {
"title": "Check Your Project Estimate",
"description": "Select your desired services and requirements to calculate estimates in real time",
"cta": "Use Estimate Calculator"
},
"contact": {
"ready_title": "Ready to Start Your Project?",
"ready_description": "Turn your ideas into reality. Experts provide the best solutions.",
"phone_consultation": "Phone Consultation",
"email_inquiry": "Email Inquiry",
"telegram_chat": "Telegram Chat",
"instant_response": "Instant response available",
"free_consultation": "Free Consultation Application",
"form": {
"name": "Name",
"email": "Email",
"phone": "Phone",
"service_interest": "Service Interest",
"service_options": {
"select": "Select Service Interest",
"web_development": "Web Development",
"mobile_app": "Mobile App",
"ui_ux_design": "UI/UX Design",
"branding": "Branding",
"consulting": "Consulting",
"other": "Other"
},
"message": "Please briefly describe your project",
"submit": "Apply for Consultation"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "Digital solution specialist leading customer success with innovative technology",
"overview": {
"title": "Creating Future with Innovation and Creativity",
"description_1": "SmartSolTech is a digital solution specialist established in 2020, supporting customer business success with innovative technology and creative ideas in web development, mobile apps, and UI/UX design.",
"description_2": "We don't just provide technology, but understand customer goals and propose optimal solutions to become partners growing together.",
"stats": {
"projects": "100+",
"projects_label": "Completed Projects",
"clients": "50+",
"clients_label": "Satisfied Customers",
"experience": "4 years",
"experience_label": "Industry Experience"
},
"mission": "Our Mission",
"mission_text": "Helping all businesses succeed in the digital age through technology",
"vision": "Our Vision",
"vision_text": "Growing as a global digital solution company representing Korea to lead digital innovation for customers worldwide"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "Core values pursued by SmartSolTech",
"innovation": {
"title": "Innovation",
"description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology."
},
"collaboration": {
"title": "Collaboration",
"description": "We create the best results through close communication and collaboration with customers."
},
"quality": {
"title": "Quality",
"description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with."
},
"growth": {
"title": "Growth",
"description": "We grow together with customers and pursue continuous learning and development."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "Introducing the SmartSolTech team with expertise and passion"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "We provide the best solutions with cutting-edge technology and proven tools",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "Become a Partner for Success Together",
"description": "Take your business to the next level with SmartSolTech",
"partnership": "Partnership Inquiry",
"portfolio": "View Portfolio"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Digital solution specialist leading innovation",
"quick_links": "Quick Links",
"services": "Services",
"contact_info": "Contact Information",
"follow_us": "Follow Us",
"rights": "All rights reserved."
},
"theme": {
"light": "Light Theme",
"dark": "Dark Theme",
"toggle": "Toggle Theme"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Loading...",
"error": "Error occurred",
"success": "Success",
"view_more": "View More",
"back": "Back",
"next": "Next",
"previous": "Previous"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "Home",
"about": "About",
"services": "Services",
"portfolio": "Portfolio",
"contact": "Contact",
"calculator": "Calculator",
"admin": "Admin"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation",
"cta_primary": "Start Project",
"cta_secondary": "View Portfolio"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "Digital solutions completed with cutting-edge technology and creative ideas",
"web_development": {
"title": "Web Development",
"description": "Modern and responsive websites and web applications development",
"price": "$5,000~"
},
"mobile_app": {
"title": "Mobile App",
"description": "Native and cross-platform apps for iOS and Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX Design",
"description": "User-centered intuitive and beautiful interface design",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Digital Marketing",
"description": "Digital marketing through SEO, social media, online advertising",
"price": "$2,000~"
},
"view_all": "View All Services"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "Check out the projects completed for customer success",
"view_details": "View Details",
"view_all": "View All Portfolio"
},
"calculator": {
"title": "Check Your Project Estimate",
"description": "Select your desired services and requirements to calculate estimates in real time",
"cta": "Use Estimate Calculator"
},
"contact": {
"ready_title": "Ready to Start Your Project?",
"ready_description": "Turn your ideas into reality. Experts provide the best solutions.",
"phone_consultation": "Phone Consultation",
"email_inquiry": "Email Inquiry",
"telegram_chat": "Telegram Chat",
"instant_response": "Instant response available",
"free_consultation": "Free Consultation Application",
"form": {
"name": "Name",
"email": "Email",
"phone": "Phone",
"service_interest": "Service Interest",
"service_options": {
"select": "Select Service Interest",
"web_development": "Web Development",
"mobile_app": "Mobile App",
"ui_ux_design": "UI/UX Design",
"branding": "Branding",
"consulting": "Consulting",
"other": "Other"
},
"message": "Please briefly describe your project",
"submit": "Apply for Consultation"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "Digital solution specialist leading customer success with innovative technology",
"overview": {
"title": "Creating Future with Innovation and Creativity",
"description_1": "SmartSolTech is a digital solution specialist established in 2020, supporting customer business success with innovative technology and creative ideas in web development, mobile apps, and UI/UX design.",
"description_2": "We don't just provide technology, but understand customer goals and propose optimal solutions to become partners growing together.",
"stats": {
"projects": "100+",
"projects_label": "Completed Projects",
"clients": "50+",
"clients_label": "Satisfied Customers",
"experience": "4 years",
"experience_label": "Industry Experience"
},
"mission": "Our Mission",
"mission_text": "Helping all businesses succeed in the digital age through technology",
"vision": "Our Vision",
"vision_text": "Growing as a global digital solution company representing Korea to lead digital innovation for customers worldwide"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "Core values pursued by SmartSolTech",
"innovation": {
"title": "Innovation",
"description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology."
},
"collaboration": {
"title": "Collaboration",
"description": "We create the best results through close communication and collaboration with customers."
},
"quality": {
"title": "Quality",
"description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with."
},
"growth": {
"title": "Growth",
"description": "We grow together with customers and pursue continuous learning and development."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "Introducing the SmartSolTech team with expertise and passion"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "We provide the best solutions with cutting-edge technology and proven tools",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "Become a Partner for Success Together",
"description": "Take your business to the next level with SmartSolTech",
"partnership": "Partnership Inquiry",
"portfolio": "View Portfolio"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Digital solution specialist leading innovation",
"quick_links": "Quick Links",
"services": "Services",
"contact_info": "Contact Information",
"follow_us": "Follow Us",
"rights": "All rights reserved."
},
"theme": {
"light": "Light Theme",
"dark": "Dark Theme",
"toggle": "Toggle Theme"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Loading...",
"error": "Error occurred",
"success": "Success",
"view_more": "View More",
"back": "Back",
"next": "Next",
"previous": "Previous"
}
}

View File

@@ -0,0 +1,223 @@
{
"navigation": {
"home": "Home",
"about": "About",
"services": "Services",
"portfolio": "Portfolio",
"contact": "Contact",
"calculator": "Calculator",
"admin": "Admin"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation",
"cta_primary": "Start Project",
"cta_secondary": "View Portfolio"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "Digital solutions completed with cutting-edge technology and creative ideas",
"web_development": {
"title": "Web Development",
"description": "Modern and responsive websites and web applications development",
"price": "$5,000~"
},
"mobile_app": {
"title": "Mobile App",
"description": "Native and cross-platform apps for iOS and Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX Design",
"description": "User-centered intuitive and beautiful interface design",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Digital Marketing",
"description": "Digital marketing through SEO, social media, online advertising",
"price": "$2,000~"
},
"view_all": "View All Services"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "Check out the projects completed for customer success",
"view_details": "View Details",
"view_all": "View All Portfolio"
},
"calculator": {
"title": "Project Cost Calculator",
"subtitle": "Select your desired services and requirements to get accurate cost estimates in real time",
"meta": {
"title": "Project Cost Calculator",
"description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator"
},
"cta": {
"title": "Check Your Project Estimate",
"subtitle": "Select your desired services and requirements to calculate costs in real time",
"button": "Use Cost Calculator"
},
"step1": {
"title": "Step 1: Service Selection",
"subtitle": "Please select the services you need (multiple selection allowed)"
},
"step2": {
"title": "Step 2: Project Details",
"subtitle": "Select project complexity and timeline"
},
"complexity": {
"title": "Project Complexity",
"simple": "Simple",
"simple_desc": "Basic features, standard design",
"medium": "Medium",
"medium_desc": "Additional features, custom design",
"complex": "Complex",
"complex_desc": "Advanced features, complex integrations"
},
"timeline": {
"title": "Development Timeline",
"standard": "Standard",
"standard_desc": "Normal development timeframe",
"rush": "Rush",
"rush_desc": "Fast development (+50%)",
"extended": "Extended",
"extended_desc": "Flexible development timeline (-20%)"
},
"result": {
"title": "Estimate Results",
"subtitle": "Here's your preliminary project cost estimate",
"estimated_price": "Estimated Price",
"price_note": "* Final cost may vary based on project details",
"summary": "Project Summary",
"selected_services": "Selected Services",
"complexity": "Complexity",
"timeline": "Timeline",
"get_quote": "Get Accurate Quote",
"recalculate": "Recalculate",
"contact_note": "Contact us for an accurate quote and to discuss project details"
},
"next_step": "Next Step",
"prev_step": "Previous",
"calculate": "Calculate"
},
"contact": {
"ready_title": "Ready to Start Your Project?",
"ready_description": "Turn your ideas into reality. Experts provide the best solutions.",
"phone_consultation": "Phone Consultation",
"email_inquiry": "Email Inquiry",
"telegram_chat": "Telegram Chat",
"instant_response": "Instant response available",
"free_consultation": "Free Consultation Application",
"form": {
"name": "Name",
"email": "Email",
"phone": "Phone",
"service_interest": "Service Interest",
"service_options": {
"select": "Select Service Interest",
"web_development": "Web Development",
"mobile_app": "Mobile App",
"ui_ux_design": "UI/UX Design",
"branding": "Branding",
"consulting": "Consulting",
"other": "Other"
},
"message": "Please briefly describe your project",
"submit": "Apply for Consultation"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "Digital solution specialist leading customer success with innovative technology",
"overview": {
"title": "Creating Future with Innovation and Creativity",
"description_1": "SmartSolTech is a digital solution specialist established in 2020, supporting customer business success with innovative technology and creative ideas in web development, mobile apps, and UI/UX design.",
"description_2": "We don't just provide technology, but understand customer goals and propose optimal solutions to become partners growing together.",
"stats": {
"projects": "100+",
"projects_label": "Completed Projects",
"clients": "50+",
"clients_label": "Satisfied Customers",
"experience": "4 years",
"experience_label": "Industry Experience"
},
"mission": "Our Mission",
"mission_text": "Helping all businesses succeed in the digital age through technology",
"vision": "Our Vision",
"vision_text": "Growing as a global digital solution company representing Korea to lead digital innovation for customers worldwide"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "Core values pursued by SmartSolTech",
"innovation": {
"title": "Innovation",
"description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology."
},
"collaboration": {
"title": "Collaboration",
"description": "We create the best results through close communication and collaboration with customers."
},
"quality": {
"title": "Quality",
"description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with."
},
"growth": {
"title": "Growth",
"description": "We grow together with customers and pursue continuous learning and development."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "Introducing the SmartSolTech team with expertise and passion"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "We provide the best solutions with cutting-edge technology and proven tools",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "Become a Partner for Success Together",
"description": "Take your business to the next level with SmartSolTech",
"partnership": "Partnership Inquiry",
"portfolio": "View Portfolio"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Digital solution specialist leading innovation",
"quick_links": "Quick Links",
"services": "Services",
"contact_info": "Contact Information",
"follow_us": "Follow Us",
"rights": "All rights reserved."
},
"theme": {
"light": "Light Theme",
"dark": "Dark Theme",
"toggle": "Toggle Theme"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Loading...",
"error": "Error occurred",
"success": "Success",
"view_more": "View More",
"back": "Back",
"next": "Next",
"previous": "Previous"
}
}

View File

@@ -0,0 +1,223 @@
{
"navigation": {
"home": "Home",
"about": "About",
"services": "Services",
"portfolio": "Portfolio",
"contact": "Contact",
"calculator": "Calculator",
"admin": "Admin"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation",
"cta_primary": "Start Project",
"cta_secondary": "View Portfolio"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "Digital solutions completed with cutting-edge technology and creative ideas",
"web_development": {
"title": "Web Development",
"description": "Modern and responsive websites and web applications development",
"price": "$5,000~"
},
"mobile_app": {
"title": "Mobile App",
"description": "Native and cross-platform apps for iOS and Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX Design",
"description": "User-centered intuitive and beautiful interface design",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Digital Marketing",
"description": "Digital marketing through SEO, social media, online advertising",
"price": "$2,000~"
},
"view_all": "View All Services"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "Check out the projects completed for customer success",
"view_details": "View Details",
"view_all": "View All Portfolio"
},
"calculator": {
"title": "Project Cost Calculator",
"subtitle": "Select your desired services and requirements to get accurate cost estimates in real time",
"meta": {
"title": "Project Cost Calculator",
"description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator"
},
"cta": {
"title": "Check Your Project Estimate",
"subtitle": "Select your desired services and requirements to calculate costs in real time",
"button": "Use Cost Calculator"
},
"step1": {
"title": "Step 1: Service Selection",
"subtitle": "Please select the services you need (multiple selection allowed)"
},
"step2": {
"title": "Step 2: Project Details",
"subtitle": "Select project complexity and timeline"
},
"complexity": {
"title": "Project Complexity",
"simple": "Simple",
"simple_desc": "Basic features, standard design",
"medium": "Medium",
"medium_desc": "Additional features, custom design",
"complex": "Complex",
"complex_desc": "Advanced features, complex integrations"
},
"timeline": {
"title": "Development Timeline",
"standard": "Standard",
"standard_desc": "Normal development timeframe",
"rush": "Rush",
"rush_desc": "Fast development (+50%)",
"extended": "Extended",
"extended_desc": "Flexible development timeline (-20%)"
},
"result": {
"title": "Estimate Results",
"subtitle": "Here's your preliminary project cost estimate",
"estimated_price": "Estimated Price",
"price_note": "* Final cost may vary based on project details",
"summary": "Project Summary",
"selected_services": "Selected Services",
"complexity": "Complexity",
"timeline": "Timeline",
"get_quote": "Get Accurate Quote",
"recalculate": "Recalculate",
"contact_note": "Contact us for an accurate quote and to discuss project details"
},
"next_step": "Next Step",
"prev_step": "Previous",
"calculate": "Calculate"
},
"contact": {
"ready_title": "Ready to Start Your Project?",
"ready_description": "Turn your ideas into reality. Experts provide the best solutions.",
"phone_consultation": "Phone Consultation",
"email_inquiry": "Email Inquiry",
"telegram_chat": "Telegram Chat",
"instant_response": "Instant response available",
"free_consultation": "Free Consultation Application",
"form": {
"name": "Name",
"email": "Email",
"phone": "Phone",
"service_interest": "Service Interest",
"service_options": {
"select": "Select Service Interest",
"web_development": "Web Development",
"mobile_app": "Mobile App",
"ui_ux_design": "UI/UX Design",
"branding": "Branding",
"consulting": "Consulting",
"other": "Other"
},
"message": "Please briefly describe your project",
"submit": "Apply for Consultation"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "Digital solution specialist leading customer success with innovative technology",
"overview": {
"title": "Creating Future with Innovation and Creativity",
"description_1": "SmartSolTech is a digital solution specialist established in 2020, supporting customer business success with innovative technology and creative ideas in web development, mobile apps, and UI/UX design.",
"description_2": "We don't just provide technology, but understand customer goals and propose optimal solutions to become partners growing together.",
"stats": {
"projects": "100+",
"projects_label": "Completed Projects",
"clients": "50+",
"clients_label": "Satisfied Customers",
"experience": "4 years",
"experience_label": "Industry Experience"
},
"mission": "Our Mission",
"mission_text": "Helping all businesses succeed in the digital age through technology",
"vision": "Our Vision",
"vision_text": "Growing as a global digital solution company representing Korea to lead digital innovation for customers worldwide"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "Core values pursued by SmartSolTech",
"innovation": {
"title": "Innovation",
"description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology."
},
"collaboration": {
"title": "Collaboration",
"description": "We create the best results through close communication and collaboration with customers."
},
"quality": {
"title": "Quality",
"description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with."
},
"growth": {
"title": "Growth",
"description": "We grow together with customers and pursue continuous learning and development."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "Introducing the SmartSolTech team with expertise and passion"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "We provide the best solutions with cutting-edge technology and proven tools",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "Become a Partner for Success Together",
"description": "Take your business to the next level with SmartSolTech",
"partnership": "Partnership Inquiry",
"portfolio": "View Portfolio"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Digital solution specialist leading innovation",
"quick_links": "Quick Links",
"services": "Services",
"contact_info": "Contact Information",
"follow_us": "Follow Us",
"rights": "All rights reserved."
},
"theme": {
"light": "Light Theme",
"dark": "Dark Theme",
"toggle": "Toggle Theme"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Loading...",
"error": "Error occurred",
"success": "Success",
"view_more": "View More",
"back": "Back",
"next": "Next",
"previous": "Previous"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "Басты бет",
"about": "Біз туралы",
"services": "Қызметтер",
"portfolio": "Портфолио",
"contact": "Байланыс",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Ақылды Технологиялық",
"subtitle": "Шешімдер",
"description": "Инновациялық веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн арқылы бизнестің цифрлық трансформациясын жүргіземіз",
"cta_primary": "Жобаны бастау",
"cta_secondary": "Портфолионы көру"
},
"services": {
"title": "Біздің",
"title_highlight": "Қызметтер",
"description": "Заманауи технология және шығармашылық идеялармен жасалған цифрлық шешімдер",
"web_development": {
"title": "Веб-әзірлеу",
"description": "Заманауи және бейімделгіш веб-сайттар мен веб-қосымшаларды әзірлеу",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильді қосымшалар",
"description": "iOS және Android үшін нативті және кросс-платформалық қосымшалар",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Пайдаланушы-орталықты интуитивті және әдемі интерфейс дизайны",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифрлық маркетинг",
"description": "SEO, әлеуметтік медиа, онлайн жарнама арқылы цифрлық маркетинг",
"price": "$2,000~"
},
"view_all": "Барлық қызметтерді көру"
},
"portfolio": {
"title": "Соңғы",
"title_highlight": "Жобалар",
"description": "Тұтынушылардың табысы үшін аяқталған жобаларды тексеріңіз",
"view_details": "Толығырақ",
"view_all": "Барлық портфолионы көру"
},
"calculator": {
"title": "Жобаңыздың бағасын тексеріңіз",
"description": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта бағаны есептеңіз",
"cta": "Баға калькуляторын пайдалану"
},
"contact": {
"ready_title": "Жобаңызды бастауға дайынсыз ба?",
"ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.",
"phone_consultation": "Телефон кеңесі",
"email_inquiry": "Электрондық пошта сұрауы",
"telegram_chat": "Telegram чаты",
"instant_response": "Лезде жауап беру мүмкін",
"free_consultation": "Тегін кеңес беру өтініші",
"form": {
"name": "Аты",
"email": "Электрондық пошта",
"phone": "Телефон",
"service_interest": "Қызығатын қызмет",
"service_options": {
"select": "Қызығатын қызметті таңдаңыз",
"web_development": "Веб-әзірлеу",
"mobile_app": "Мобильді қосымша",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Кеңес беру",
"other": "Басқа"
},
"message": "Жобаңыз туралы қысқаша сипаттаңыз",
"submit": "Кеңес беру үшін өтініш беру"
}
},
"about": {
"hero_title": "Туралы",
"hero_highlight": "SmartSolTech",
"hero_description": "Инновациялық технологиямен тұтынушылардың табысына жетелейтін цифрлық шешімдер маманы",
"overview": {
"title": "Инновация мен шығармашылықпен болашақты құру",
"description_1": "SmartSolTech - 2020 жылы құрылған цифрлық шешімдер маманы, веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн салаларында инновациялық технология мен шығармашылық идеялар негізінде тұтынушылардың бизнес табысын қолдайды.",
"description_2": "Біз жай ғана технологияны ұсынып қана қоймаймыз, тұтынушылардың мақсаттарын түсініп, оларға сәйкес оңтайлы шешімдерді ұсынып, бірге өсетін серіктестер болуды мақсат етеміз.",
"stats": {
"projects": "100+",
"projects_label": "Аяқталған жобалар",
"clients": "50+",
"clients_label": "Қанағаттанған тұтынушылар",
"experience": "4 жыл",
"experience_label": "Саладағы тәжірибе"
},
"mission": "Біздің миссия",
"mission_text": "Технология арқылы барлық бизнестердің цифрлық дәуірде табысқа жетуіне көмектесу",
"vision": "Біздің көзқарас",
"vision_text": "Қазақстанды білдіретін жаһандық цифрлық шешімдер компаниясы ретінде өсіп, бүкіл әлемдегі тұтынушылардың цифрлық инновацияларын басқару"
},
"values": {
"title": "Негізгі",
"title_highlight": "Құндылықтар",
"description": "SmartSolTech ұстанатын негізгі құндылықтар",
"innovation": {
"title": "Инновация",
"description": "Үздіксіз зерттеу-әзірлеу және заманауи технологияларды енгізу арқылы инновациялық шешімдерді ұсынамыз."
},
"collaboration": {
"title": "Ынтымақтастық",
"description": "Тұтынушылармен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз."
},
"quality": {
"title": "Сапа",
"description": "Жоғары сапа стандарттарын сақтап, тұтынушылар қанағаттана алатын жоғары сапалы өнімдерді ұсынамыз."
},
"growth": {
"title": "Өсу",
"description": "Тұтынушылармен бірге өсіп, үздіксіз оқу мен дамуды мақсат етеміз."
}
},
"team": {
"title": "Біздің",
"title_highlight": "Команда",
"description": "Сараптама мен құлшынысты SmartSolTech командасын таныстырамыз"
},
"tech_stack": {
"title": "Технологиялық",
"title_highlight": "Стек",
"description": "Заманауи технология мен дәлелденген құралдармен ең жақсы шешімдерді ұсынамыз",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильді"
},
"cta": {
"title": "Бірге табысқа жететін серіктес болыңыз",
"description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз",
"partnership": "Серіктестік сұрауы",
"portfolio": "Портфолионы көру"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Инновацияны басқаратын цифрлық шешімдер маманы",
"quick_links": "Жылдам сілтемелер",
"services": "Қызметтер",
"contact_info": "Байланыс ақпараты",
"follow_us": "Бізді іздеңіз",
"rights": "Барлық құқықтар сақталған."
},
"theme": {
"light": "Ашық тема",
"dark": "Қараңғы тема",
"toggle": "Теманы ауыстыру"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Жүктелуде...",
"error": "Қате орын алды",
"success": "Сәтті",
"view_more": "Көбірек көру",
"back": "Артқа",
"next": "Келесі",
"previous": "Алдыңғы"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "Басты бет",
"about": "Біз туралы",
"services": "Қызметтер",
"portfolio": "Портфолио",
"contact": "Байланыс",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Ақылды Технологиялық",
"subtitle": "Шешімдер",
"description": "Инновациялық веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн арқылы бизнестің цифрлық трансформациясын жүргіземіз",
"cta_primary": "Жобаны бастау",
"cta_secondary": "Портфолионы көру"
},
"services": {
"title": "Біздің",
"title_highlight": "Қызметтер",
"description": "Заманауи технология және шығармашылық идеялармен жасалған цифрлық шешімдер",
"web_development": {
"title": "Веб-әзірлеу",
"description": "Заманауи және бейімделгіш веб-сайттар мен веб-қосымшаларды әзірлеу",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильді қосымшалар",
"description": "iOS және Android үшін нативті және кросс-платформалық қосымшалар",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Пайдаланушы-орталықты интуитивті және әдемі интерфейс дизайны",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифрлық маркетинг",
"description": "SEO, әлеуметтік медиа, онлайн жарнама арқылы цифрлық маркетинг",
"price": "$2,000~"
},
"view_all": "Барлық қызметтерді көру"
},
"portfolio": {
"title": "Соңғы",
"title_highlight": "Жобалар",
"description": "Тұтынушылардың табысы үшін аяқталған жобаларды тексеріңіз",
"view_details": "Толығырақ",
"view_all": "Барлық портфолионы көру"
},
"calculator": {
"title": "Жобаңыздың бағасын тексеріңіз",
"description": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта бағаны есептеңіз",
"cta": "Баға калькуляторын пайдалану"
},
"contact": {
"ready_title": "Жобаңызды бастауға дайынсыз ба?",
"ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.",
"phone_consultation": "Телефон кеңесі",
"email_inquiry": "Электрондық пошта сұрауы",
"telegram_chat": "Telegram чаты",
"instant_response": "Лезде жауап беру мүмкін",
"free_consultation": "Тегін кеңес беру өтініші",
"form": {
"name": "Аты",
"email": "Электрондық пошта",
"phone": "Телефон",
"service_interest": "Қызығатын қызмет",
"service_options": {
"select": "Қызығатын қызметті таңдаңыз",
"web_development": "Веб-әзірлеу",
"mobile_app": "Мобильді қосымша",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Кеңес беру",
"other": "Басқа"
},
"message": "Жобаңыз туралы қысқаша сипаттаңыз",
"submit": "Кеңес беру үшін өтініш беру"
}
},
"about": {
"hero_title": "Туралы",
"hero_highlight": "SmartSolTech",
"hero_description": "Инновациялық технологиямен тұтынушылардың табысына жетелейтін цифрлық шешімдер маманы",
"overview": {
"title": "Инновация мен шығармашылықпен болашақты құру",
"description_1": "SmartSolTech - 2020 жылы құрылған цифрлық шешімдер маманы, веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн салаларында инновациялық технология мен шығармашылық идеялар негізінде тұтынушылардың бизнес табысын қолдайды.",
"description_2": "Біз жай ғана технологияны ұсынып қана қоймаймыз, тұтынушылардың мақсаттарын түсініп, оларға сәйкес оңтайлы шешімдерді ұсынып, бірге өсетін серіктестер болуды мақсат етеміз.",
"stats": {
"projects": "100+",
"projects_label": "Аяқталған жобалар",
"clients": "50+",
"clients_label": "Қанағаттанған тұтынушылар",
"experience": "4 жыл",
"experience_label": "Саладағы тәжірибе"
},
"mission": "Біздің миссия",
"mission_text": "Технология арқылы барлық бизнестердің цифрлық дәуірде табысқа жетуіне көмектесу",
"vision": "Біздің көзқарас",
"vision_text": "Қазақстанды білдіретін жаһандық цифрлық шешімдер компаниясы ретінде өсіп, бүкіл әлемдегі тұтынушылардың цифрлық инновацияларын басқару"
},
"values": {
"title": "Негізгі",
"title_highlight": "Құндылықтар",
"description": "SmartSolTech ұстанатын негізгі құндылықтар",
"innovation": {
"title": "Инновация",
"description": "Үздіксіз зерттеу-әзірлеу және заманауи технологияларды енгізу арқылы инновациялық шешімдерді ұсынамыз."
},
"collaboration": {
"title": "Ынтымақтастық",
"description": "Тұтынушылармен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз."
},
"quality": {
"title": "Сапа",
"description": "Жоғары сапа стандарттарын сақтап, тұтынушылар қанағаттана алатын жоғары сапалы өнімдерді ұсынамыз."
},
"growth": {
"title": "Өсу",
"description": "Тұтынушылармен бірге өсіп, үздіксіз оқу мен дамуды мақсат етеміз."
}
},
"team": {
"title": "Біздің",
"title_highlight": "Команда",
"description": "Сараптама мен құлшынысты SmartSolTech командасын таныстырамыз"
},
"tech_stack": {
"title": "Технологиялық",
"title_highlight": "Стек",
"description": "Заманауи технология мен дәлелденген құралдармен ең жақсы шешімдерді ұсынамыз",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильді"
},
"cta": {
"title": "Бірге табысқа жететін серіктес болыңыз",
"description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз",
"partnership": "Серіктестік сұрауы",
"portfolio": "Портфолионы көру"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Инновацияны басқаратын цифрлық шешімдер маманы",
"quick_links": "Жылдам сілтемелер",
"services": "Қызметтер",
"contact_info": "Байланыс ақпараты",
"follow_us": "Бізді іздеңіз",
"rights": "Барлық құқықтар сақталған."
},
"theme": {
"light": "Ашық тема",
"dark": "Қараңғы тема",
"toggle": "Теманы ауыстыру"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Жүктелуде...",
"error": "Қате орын алды",
"success": "Сәтті",
"view_more": "Көбірек көру",
"back": "Артқа",
"next": "Келесі",
"previous": "Алдыңғы"
}
}

View File

@@ -0,0 +1,223 @@
{
"navigation": {
"home": "Басты бет",
"about": "Біз туралы",
"services": "Қызметтер",
"portfolio": "Портфолио",
"contact": "Байланыс",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Ақылды Технологиялық",
"subtitle": "Шешімдер",
"description": "Инновациялық веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн арқылы бизнестің цифрлық трансформациясын жүргіземіз",
"cta_primary": "Жобаны бастау",
"cta_secondary": "Портфолионы көру"
},
"services": {
"title": "Біздің",
"title_highlight": "Қызметтер",
"description": "Заманауи технология және шығармашылық идеялармен жасалған цифрлық шешімдер",
"web_development": {
"title": "Веб-әзірлеу",
"description": "Заманауи және бейімделгіш веб-сайттар мен веб-қосымшаларды әзірлеу",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильді қосымшалар",
"description": "iOS және Android үшін нативті және кросс-платформалық қосымшалар",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Пайдаланушы-орталықты интуитивті және әдемі интерфейс дизайны",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифрлық маркетинг",
"description": "SEO, әлеуметтік медиа, онлайн жарнама арқылы цифрлық маркетинг",
"price": "$2,000~"
},
"view_all": "Барлық қызметтерді көру"
},
"portfolio": {
"title": "Соңғы",
"title_highlight": "Жобалар",
"description": "Тұтынушылардың табысы үшін аяқталған жобаларды тексеріңіз",
"view_details": "Толығырақ",
"view_all": "Барлық портфолионы көру"
},
"calculator": {
"title": "Жоба Құнының Калькуляторы",
"subtitle": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта дәл бағаны алыңыз",
"meta": {
"title": "Жоба құнының калькуляторы",
"description": "Веб-әзірлеу, мобильді қосымша немесе дизайн жобасының құнын біздің интерактивті калькулятормен есептеңіз"
},
"cta": {
"title": "Жобаның бағасын тексеріңіз",
"subtitle": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта бағаны есептейміз",
"button": "Құн калькуляторын пайдалану"
},
"step1": {
"title": "1-қадам: Қызмет таңдау",
"subtitle": "Қажетті қызметтерді таңдаңыз (бірнеше таңдауға болады)"
},
"step2": {
"title": "2-қадам: Жоба мәліметтері",
"subtitle": "Жобаның күрделілігі мен мерзімін таңдаңыз"
},
"complexity": {
"title": "Жобаның күрделілігі",
"simple": "Қарапайым",
"simple_desc": "Негізгі функциялар, стандартты дизайн",
"medium": "Орташа",
"medium_desc": "Қосымша функциялар, жеке дизайн",
"complex": "Күрделі",
"complex_desc": "Кеңейтілген функциялар, күрделі интеграциялар"
},
"timeline": {
"title": "Әзірлеу мерзімі",
"standard": "Стандартты",
"standard_desc": "Қалыпты әзірлеу мерзімі",
"rush": "Асығыс",
"rush_desc": "Жылдам әзірлеу (+50%)",
"extended": "Кеңейтілген",
"extended_desc": "Икемді әзірлеу мерзімі (-20%)"
},
"result": {
"title": "Есептеу нәтижесі",
"subtitle": "Міне, сіздің алдын ала жоба құнының бағасы",
"estimated_price": "Алдын ала баға",
"price_note": "* Соңғы құн жоба мәліметтеріне байланысты өзгеруі мүмкін",
"summary": "Жоба қорытындысы",
"selected_services": "Таңдалған қызметтер",
"complexity": "Күрделілік",
"timeline": "Мерзім",
"get_quote": "Дәл ұсыныс алу",
"recalculate": "Қайта есептеу",
"contact_note": "Дәл ұсыныс алу және жоба мәліметтерін талқылау үшін бізбен байланысыңыз"
},
"next_step": "Келесі қадам",
"prev_step": "Артқа",
"calculate": "Есептеу"
},
"contact": {
"ready_title": "Жобаңызды бастауға дайынсыз ба?",
"ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.",
"phone_consultation": "Телефон кеңесі",
"email_inquiry": "Электрондық пошта сұрауы",
"telegram_chat": "Telegram чаты",
"instant_response": "Лезде жауап беру мүмкін",
"free_consultation": "Тегін кеңес беру өтініші",
"form": {
"name": "Аты",
"email": "Электрондық пошта",
"phone": "Телефон",
"service_interest": "Қызығатын қызмет",
"service_options": {
"select": "Қызығатын қызметті таңдаңыз",
"web_development": "Веб-әзірлеу",
"mobile_app": "Мобильді қосымша",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Кеңес беру",
"other": "Басқа"
},
"message": "Жобаңыз туралы қысқаша сипаттаңыз",
"submit": "Кеңес беру үшін өтініш беру"
}
},
"about": {
"hero_title": "Туралы",
"hero_highlight": "SmartSolTech",
"hero_description": "Инновациялық технологиямен тұтынушылардың табысына жетелейтін цифрлық шешімдер маманы",
"overview": {
"title": "Инновация мен шығармашылықпен болашақты құру",
"description_1": "SmartSolTech - 2020 жылы құрылған цифрлық шешімдер маманы, веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн салаларында инновациялық технология мен шығармашылық идеялар негізінде тұтынушылардың бизнес табысын қолдайды.",
"description_2": "Біз жай ғана технологияны ұсынып қана қоймаймыз, тұтынушылардың мақсаттарын түсініп, оларға сәйкес оңтайлы шешімдерді ұсынып, бірге өсетін серіктестер болуды мақсат етеміз.",
"stats": {
"projects": "100+",
"projects_label": "Аяқталған жобалар",
"clients": "50+",
"clients_label": "Қанағаттанған тұтынушылар",
"experience": "4 жыл",
"experience_label": "Саладағы тәжірибе"
},
"mission": "Біздің миссия",
"mission_text": "Технология арқылы барлық бизнестердің цифрлық дәуірде табысқа жетуіне көмектесу",
"vision": "Біздің көзқарас",
"vision_text": "Қазақстанды білдіретін жаһандық цифрлық шешімдер компаниясы ретінде өсіп, бүкіл әлемдегі тұтынушылардың цифрлық инновацияларын басқару"
},
"values": {
"title": "Негізгі",
"title_highlight": "Құндылықтар",
"description": "SmartSolTech ұстанатын негізгі құндылықтар",
"innovation": {
"title": "Инновация",
"description": "Үздіксіз зерттеу-әзірлеу және заманауи технологияларды енгізу арқылы инновациялық шешімдерді ұсынамыз."
},
"collaboration": {
"title": "Ынтымақтастық",
"description": "Тұтынушылармен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз."
},
"quality": {
"title": "Сапа",
"description": "Жоғары сапа стандарттарын сақтап, тұтынушылар қанағаттана алатын жоғары сапалы өнімдерді ұсынамыз."
},
"growth": {
"title": "Өсу",
"description": "Тұтынушылармен бірге өсіп, үздіксіз оқу мен дамуды мақсат етеміз."
}
},
"team": {
"title": "Біздің",
"title_highlight": "Команда",
"description": "Сараптама мен құлшынысты SmartSolTech командасын таныстырамыз"
},
"tech_stack": {
"title": "Технологиялық",
"title_highlight": "Стек",
"description": "Заманауи технология мен дәлелденген құралдармен ең жақсы шешімдерді ұсынамыз",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильді"
},
"cta": {
"title": "Бірге табысқа жететін серіктес болыңыз",
"description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз",
"partnership": "Серіктестік сұрауы",
"portfolio": "Портфолионы көру"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Инновацияны басқаратын цифрлық шешімдер маманы",
"quick_links": "Жылдам сілтемелер",
"services": "Қызметтер",
"contact_info": "Байланыс ақпараты",
"follow_us": "Бізді іздеңіз",
"rights": "Барлық құқықтар сақталған."
},
"theme": {
"light": "Ашық тема",
"dark": "Қараңғы тема",
"toggle": "Теманы ауыстыру"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Жүктелуде...",
"error": "Қате орын алды",
"success": "Сәтті",
"view_more": "Көбірек көру",
"back": "Артқа",
"next": "Келесі",
"previous": "Алдыңғы"
}
}

View File

@@ -0,0 +1,223 @@
{
"navigation": {
"home": "Басты бет",
"about": "Біз туралы",
"services": "Қызметтер",
"portfolio": "Портфолио",
"contact": "Байланыс",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Ақылды Технологиялық",
"subtitle": "Шешімдер",
"description": "Инновациялық веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн арқылы бизнестің цифрлық трансформациясын жүргіземіз",
"cta_primary": "Жобаны бастау",
"cta_secondary": "Портфолионы көру"
},
"services": {
"title": "Біздің",
"title_highlight": "Қызметтер",
"description": "Заманауи технология және шығармашылық идеялармен жасалған цифрлық шешімдер",
"web_development": {
"title": "Веб-әзірлеу",
"description": "Заманауи және бейімделгіш веб-сайттар мен веб-қосымшаларды әзірлеу",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильді қосымшалар",
"description": "iOS және Android үшін нативті және кросс-платформалық қосымшалар",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Пайдаланушы-орталықты интуитивті және әдемі интерфейс дизайны",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифрлық маркетинг",
"description": "SEO, әлеуметтік медиа, онлайн жарнама арқылы цифрлық маркетинг",
"price": "$2,000~"
},
"view_all": "Барлық қызметтерді көру"
},
"portfolio": {
"title": "Соңғы",
"title_highlight": "Жобалар",
"description": "Тұтынушылардың табысы үшін аяқталған жобаларды тексеріңіз",
"view_details": "Толығырақ",
"view_all": "Барлық портфолионы көру"
},
"calculator": {
"title": "Жоба Құнының Калькуляторы",
"subtitle": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта дәл бағаны алыңыз",
"meta": {
"title": "Жоба құнының калькуляторы",
"description": "Веб-әзірлеу, мобильді қосымша немесе дизайн жобасының құнын біздің интерактивті калькулятормен есептеңіз"
},
"cta": {
"title": "Жобаның бағасын тексеріңіз",
"subtitle": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта бағаны есептейміз",
"button": "Құн калькуляторын пайдалану"
},
"step1": {
"title": "1-қадам: Қызмет таңдау",
"subtitle": "Қажетті қызметтерді таңдаңыз (бірнеше таңдауға болады)"
},
"step2": {
"title": "2-қадам: Жоба мәліметтері",
"subtitle": "Жобаның күрделілігі мен мерзімін таңдаңыз"
},
"complexity": {
"title": "Жобаның күрделілігі",
"simple": "Қарапайым",
"simple_desc": "Негізгі функциялар, стандартты дизайн",
"medium": "Орташа",
"medium_desc": "Қосымша функциялар, жеке дизайн",
"complex": "Күрделі",
"complex_desc": "Кеңейтілген функциялар, күрделі интеграциялар"
},
"timeline": {
"title": "Әзірлеу мерзімі",
"standard": "Стандартты",
"standard_desc": "Қалыпты әзірлеу мерзімі",
"rush": "Асығыс",
"rush_desc": "Жылдам әзірлеу (+50%)",
"extended": "Кеңейтілген",
"extended_desc": "Икемді әзірлеу мерзімі (-20%)"
},
"result": {
"title": "Есептеу нәтижесі",
"subtitle": "Міне, сіздің алдын ала жоба құнының бағасы",
"estimated_price": "Алдын ала баға",
"price_note": "* Соңғы құн жоба мәліметтеріне байланысты өзгеруі мүмкін",
"summary": "Жоба қорытындысы",
"selected_services": "Таңдалған қызметтер",
"complexity": "Күрделілік",
"timeline": "Мерзім",
"get_quote": "Дәл ұсыныс алу",
"recalculate": "Қайта есептеу",
"contact_note": "Дәл ұсыныс алу және жоба мәліметтерін талқылау үшін бізбен байланысыңыз"
},
"next_step": "Келесі қадам",
"prev_step": "Артқа",
"calculate": "Есептеу"
},
"contact": {
"ready_title": "Жобаңызды бастауға дайынсыз ба?",
"ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.",
"phone_consultation": "Телефон кеңесі",
"email_inquiry": "Электрондық пошта сұрауы",
"telegram_chat": "Telegram чаты",
"instant_response": "Лезде жауап беру мүмкін",
"free_consultation": "Тегін кеңес беру өтініші",
"form": {
"name": "Аты",
"email": "Электрондық пошта",
"phone": "Телефон",
"service_interest": "Қызығатын қызмет",
"service_options": {
"select": "Қызығатын қызметті таңдаңыз",
"web_development": "Веб-әзірлеу",
"mobile_app": "Мобильді қосымша",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Кеңес беру",
"other": "Басқа"
},
"message": "Жобаңыз туралы қысқаша сипаттаңыз",
"submit": "Кеңес беру үшін өтініш беру"
}
},
"about": {
"hero_title": "Туралы",
"hero_highlight": "SmartSolTech",
"hero_description": "Инновациялық технологиямен тұтынушылардың табысына жетелейтін цифрлық шешімдер маманы",
"overview": {
"title": "Инновация мен шығармашылықпен болашақты құру",
"description_1": "SmartSolTech - 2020 жылы құрылған цифрлық шешімдер маманы, веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн салаларында инновациялық технология мен шығармашылық идеялар негізінде тұтынушылардың бизнес табысын қолдайды.",
"description_2": "Біз жай ғана технологияны ұсынып қана қоймаймыз, тұтынушылардың мақсаттарын түсініп, оларға сәйкес оңтайлы шешімдерді ұсынып, бірге өсетін серіктестер болуды мақсат етеміз.",
"stats": {
"projects": "100+",
"projects_label": "Аяқталған жобалар",
"clients": "50+",
"clients_label": "Қанағаттанған тұтынушылар",
"experience": "4 жыл",
"experience_label": "Саладағы тәжірибе"
},
"mission": "Біздің миссия",
"mission_text": "Технология арқылы барлық бизнестердің цифрлық дәуірде табысқа жетуіне көмектесу",
"vision": "Біздің көзқарас",
"vision_text": "Қазақстанды білдіретін жаһандық цифрлық шешімдер компаниясы ретінде өсіп, бүкіл әлемдегі тұтынушылардың цифрлық инновацияларын басқару"
},
"values": {
"title": "Негізгі",
"title_highlight": "Құндылықтар",
"description": "SmartSolTech ұстанатын негізгі құндылықтар",
"innovation": {
"title": "Инновация",
"description": "Үздіксіз зерттеу-әзірлеу және заманауи технологияларды енгізу арқылы инновациялық шешімдерді ұсынамыз."
},
"collaboration": {
"title": "Ынтымақтастық",
"description": "Тұтынушылармен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз."
},
"quality": {
"title": "Сапа",
"description": "Жоғары сапа стандарттарын сақтап, тұтынушылар қанағаттана алатын жоғары сапалы өнімдерді ұсынамыз."
},
"growth": {
"title": "Өсу",
"description": "Тұтынушылармен бірге өсіп, үздіксіз оқу мен дамуды мақсат етеміз."
}
},
"team": {
"title": "Біздің",
"title_highlight": "Команда",
"description": "Сараптама мен құлшынысты SmartSolTech командасын таныстырамыз"
},
"tech_stack": {
"title": "Технологиялық",
"title_highlight": "Стек",
"description": "Заманауи технология мен дәлелденген құралдармен ең жақсы шешімдерді ұсынамыз",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильді"
},
"cta": {
"title": "Бірге табысқа жететін серіктес болыңыз",
"description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз",
"partnership": "Серіктестік сұрауы",
"portfolio": "Портфолионы көру"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Инновацияны басқаратын цифрлық шешімдер маманы",
"quick_links": "Жылдам сілтемелер",
"services": "Қызметтер",
"contact_info": "Байланыс ақпараты",
"follow_us": "Бізді іздеңіз",
"rights": "Барлық құқықтар сақталған."
},
"theme": {
"light": "Ашық тема",
"dark": "Қараңғы тема",
"toggle": "Теманы ауыстыру"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Жүктелуде...",
"error": "Қате орын алды",
"success": "Сәтті",
"view_more": "Көбірек көру",
"back": "Артқа",
"next": "Келесі",
"previous": "Алдыңғы"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "홈",
"about": "회사소개",
"services": "서비스",
"portfolio": "포트폴리오",
"contact": "연락처",
"calculator": "견적계산기",
"admin": "관리자"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다",
"cta_primary": "프로젝트 시작하기",
"cta_secondary": "포트폴리오 보기"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션",
"web_development": {
"title": "웹 개발",
"description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발",
"price": "₩500,000~"
},
"mobile_app": {
"title": "모바일 앱",
"description": "iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱",
"price": "₩800,000~"
},
"ui_ux_design": {
"title": "UI/UX 디자인",
"description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인",
"price": "₩300,000~"
},
"digital_marketing": {
"title": "디지털 마케팅",
"description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅",
"price": "₩200,000~"
},
"view_all": "모든 서비스 보기"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "고객의 성공을 위해 완성한 프로젝트들을 확인해보세요",
"view_details": "자세히 보기",
"view_all": "전체 포트폴리오 보기"
},
"calculator": {
"title": "프로젝트 견적을 확인해보세요",
"description": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"cta": "견적 계산기 사용하기"
},
"contact": {
"ready_title": "프로젝트를 시작할 준비가 되셨나요?",
"ready_description": "아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다.",
"phone_consultation": "전화 상담",
"email_inquiry": "이메일 문의",
"telegram_chat": "텔레그램 채팅",
"instant_response": "즉시 답변 가능",
"free_consultation": "무료 상담 신청",
"form": {
"name": "이름",
"email": "이메일",
"phone": "연락처",
"service_interest": "관심 서비스",
"service_options": {
"select": "관심 서비스 선택",
"web_development": "웹 개발",
"mobile_app": "모바일 앱",
"ui_ux_design": "UI/UX 디자인",
"branding": "브랜딩",
"consulting": "컨설팅",
"other": "기타"
},
"message": "프로젝트에 대해 간단히 설명해주세요",
"submit": "상담 신청하기"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업",
"overview": {
"title": "혁신과 창의로 만드는 미래",
"description_1": "SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다.",
"description_2": "우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 제안하여 함께 성장하는 파트너가 되고자 합니다.",
"stats": {
"projects": "100+",
"projects_label": "완료 프로젝트",
"clients": "50+",
"clients_label": "만족한 고객",
"experience": "4년",
"experience_label": "업계 경험"
},
"mission": "우리의 미션",
"mission_text": "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것",
"vision": "우리의 비전",
"vision_text": "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 전 세계 고객들의 디지털 혁신을 주도하는 것"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "SmartSolTech가 추구하는 핵심 가치들",
"innovation": {
"title": "혁신 (Innovation)",
"description": "끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다."
},
"collaboration": {
"title": "협력 (Collaboration)",
"description": "고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다."
},
"quality": {
"title": "품질 (Quality)",
"description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다."
},
"growth": {
"title": "성장 (Growth)",
"description": "고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "함께 성공하는 파트너가 되어보세요",
"description": "SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요",
"partnership": "파트너십 문의",
"portfolio": "포트폴리오 보기"
}
},
"footer": {
"company": "SmartSolTech",
"description": "혁신을 이끌어가는 디지털 솔루션 전문 기업",
"quick_links": "빠른 링크",
"services": "서비스",
"contact_info": "연락처 정보",
"follow_us": "팔로우하기",
"rights": "모든 권리 보유."
},
"theme": {
"light": "라이트 테마",
"dark": "다크 테마",
"toggle": "테마 전환"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "로딩 중...",
"error": "오류가 발생했습니다",
"success": "성공",
"view_more": "더 보기",
"back": "뒤로",
"next": "다음",
"previous": "이전"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "홈",
"about": "회사소개",
"services": "서비스",
"portfolio": "포트폴리오",
"contact": "연락처",
"calculator": "견적계산기",
"admin": "관리자"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다",
"cta_primary": "프로젝트 시작하기",
"cta_secondary": "포트폴리오 보기"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션",
"web_development": {
"title": "웹 개발",
"description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발",
"price": "₩500,000~"
},
"mobile_app": {
"title": "모바일 앱",
"description": "iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱",
"price": "₩800,000~"
},
"ui_ux_design": {
"title": "UI/UX 디자인",
"description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인",
"price": "₩300,000~"
},
"digital_marketing": {
"title": "디지털 마케팅",
"description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅",
"price": "₩200,000~"
},
"view_all": "모든 서비스 보기"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "고객의 성공을 위해 완성한 프로젝트들을 확인해보세요",
"view_details": "자세히 보기",
"view_all": "전체 포트폴리오 보기"
},
"calculator": {
"title": "프로젝트 견적을 확인해보세요",
"description": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"cta": "견적 계산기 사용하기"
},
"contact": {
"ready_title": "프로젝트를 시작할 준비가 되셨나요?",
"ready_description": "아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다.",
"phone_consultation": "전화 상담",
"email_inquiry": "이메일 문의",
"telegram_chat": "텔레그램 채팅",
"instant_response": "즉시 답변 가능",
"free_consultation": "무료 상담 신청",
"form": {
"name": "이름",
"email": "이메일",
"phone": "연락처",
"service_interest": "관심 서비스",
"service_options": {
"select": "관심 서비스 선택",
"web_development": "웹 개발",
"mobile_app": "모바일 앱",
"ui_ux_design": "UI/UX 디자인",
"branding": "브랜딩",
"consulting": "컨설팅",
"other": "기타"
},
"message": "프로젝트에 대해 간단히 설명해주세요",
"submit": "상담 신청하기"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업",
"overview": {
"title": "혁신과 창의로 만드는 미래",
"description_1": "SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다.",
"description_2": "우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 제안하여 함께 성장하는 파트너가 되고자 합니다.",
"stats": {
"projects": "100+",
"projects_label": "완료 프로젝트",
"clients": "50+",
"clients_label": "만족한 고객",
"experience": "4년",
"experience_label": "업계 경험"
},
"mission": "우리의 미션",
"mission_text": "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것",
"vision": "우리의 비전",
"vision_text": "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 전 세계 고객들의 디지털 혁신을 주도하는 것"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "SmartSolTech가 추구하는 핵심 가치들",
"innovation": {
"title": "혁신 (Innovation)",
"description": "끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다."
},
"collaboration": {
"title": "협력 (Collaboration)",
"description": "고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다."
},
"quality": {
"title": "품질 (Quality)",
"description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다."
},
"growth": {
"title": "성장 (Growth)",
"description": "고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "함께 성공하는 파트너가 되어보세요",
"description": "SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요",
"partnership": "파트너십 문의",
"portfolio": "포트폴리오 보기"
}
},
"footer": {
"company": "SmartSolTech",
"description": "혁신을 이끌어가는 디지털 솔루션 전문 기업",
"quick_links": "빠른 링크",
"services": "서비스",
"contact_info": "연락처 정보",
"follow_us": "팔로우하기",
"rights": "모든 권리 보유."
},
"theme": {
"light": "라이트 테마",
"dark": "다크 테마",
"toggle": "테마 전환"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "로딩 중...",
"error": "오류가 발생했습니다",
"success": "성공",
"view_more": "더 보기",
"back": "뒤로",
"next": "다음",
"previous": "이전"
}
}

View File

@@ -0,0 +1,227 @@
{
"navigation": {
"home": "홈",
"about": "calculator": {
"title": "프로젝트 견적 계산기",
"subtitle": "원하는 서비스와 요구사항을 선택하면 실시간으로 정확한 견적을 계산해드립니다",
"meta": {
"title": "프로젝트 견적 계산기",
"description": "웹 개발, 모바일 앱, 디자인 프로젝트 비용을 실시간으로 계산해보세요"
},
"cta": {
"title": "프로젝트 견적을 확인해보세요",
"subtitle": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"button": "견적 계산기 사용하기"
},
"step1": {
"title": "1단계: 서비스 선택",
"subtitle": "필요한 서비스를 선택해주세요 (복수 선택 가능)"
},
"step2": {
"title": "2단계: 프로젝트 세부사항",
"subtitle": "프로젝트의 복잡도와 일정을 선택해주세요"
},
"complexity": {
"title": "프로젝트 복잡도",
"simple": "단순",
"simple_desc": "기본 기능, 표준 디자인",
"medium": "보통",
"medium_desc": "추가 기능, 커스텀 디자인",
"complex": "복잡",
"complex_desc": "고급 기능, 복합 통합"
},
"timeline": {
"title": "개발 일정",
"standard": "표준",
"standard_desc": "일반적인 개발 기간",
"rush": "급한",
"rush_desc": "빠른 개발 (+50%)",
"extended": "여유",
"extended_desc": "넉넉한 개발 기간 (-20%)"
},
"result": {
"title": "견적 결과",
"subtitle": "프로젝트 예상 견적입니다",
"estimated_price": "예상 견적",
"price_note": "* 최종 견적은 프로젝트 세부사항에 따라 달라질 수 있습니다",
"summary": "프로젝트 요약",
"selected_services": "선택한 서비스",
"complexity": "복잡도",
"timeline": "일정",
"get_quote": "정확한 견적 받기",
"recalculate": "다시 계산",
"contact_note": "정확한 견적과 프로젝트 상담을 위해 연락주세요"
},
"next_step": "다음 단계",
"prev_step": "이전",
"calculate": "계산하기"
},",
"services": "",
"portfolio": "",
"contact": "",
"calculator": "",
"admin": ""
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": " , , UI/UX ",
"cta_primary": " ",
"cta_secondary": " "
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": " ",
"web_development": {
"title": " ",
"description": " ",
"price": "500,000~"
},
"mobile_app": {
"title": " ",
"description": "iOS Android ",
"price": "800,000~"
},
"ui_ux_design": {
"title": "UI/UX ",
"description": " ",
"price": "300,000~"
},
"digital_marketing": {
"title": " ",
"description": "SEO, , ",
"price": "200,000~"
},
"view_all": " "
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": " ",
"view_details": " ",
"view_all": " "
},
"calculator": {
"title": " ",
"description": " ",
"cta": " "
},
"contact": {
"ready_title": " ?",
"ready_description": " . .",
"phone_consultation": " ",
"email_inquiry": " ",
"telegram_chat": " ",
"instant_response": " ",
"free_consultation": " ",
"form": {
"name": "",
"email": "",
"phone": "",
"service_interest": " ",
"service_options": {
"select": " ",
"web_development": " ",
"mobile_app": " ",
"ui_ux_design": "UI/UX ",
"branding": "",
"consulting": "",
"other": ""
},
"message": " ",
"submit": " "
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": " ",
"overview": {
"title": " ",
"description_1": "SmartSolTech 2020 , , , UI/UX .",
"description_2": " , .",
"stats": {
"projects": "100+",
"projects_label": " ",
"clients": "50+",
"clients_label": " ",
"experience": "4",
"experience_label": " "
},
"mission": " ",
"mission_text": " ",
"vision": " ",
"vision_text": " "
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "SmartSolTech ",
"innovation": {
"title": " (Innovation)",
"description": " ."
},
"collaboration": {
"title": " (Collaboration)",
"description": " ."
},
"quality": {
"title": " (Quality)",
"description": " ."
},
"growth": {
"title": " (Growth)",
"description": " , ."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": " SmartSolTech "
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": " ",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": " ",
"description": "SmartSolTech ",
"partnership": " ",
"portfolio": " "
}
},
"footer": {
"company": "SmartSolTech",
"description": " ",
"quick_links": " ",
"services": "",
"contact_info": " ",
"follow_us": "",
"rights": " ."
},
"theme": {
"light": " ",
"dark": " ",
"toggle": " "
},
"language": {
"english": "English",
"korean": "",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": " ...",
"error": " ",
"success": "",
"view_more": " ",
"back": "",
"next": "",
"previous": ""
}
}

View File

@@ -0,0 +1,230 @@
{
"navigation": {
"home": "홈",
"about": "calculator": {
"title": "프로젝트 견적 계산기",
"subtitle": "원하는 서비스와 요구사항을 선택하면 실시간으로 정확한 견적을 계산해드립니다",
"meta": {
"title": "프로젝트 견적 계산기",
"description": "웹 개발, 모바일 앱, 디자인 프로젝트 비용을 실시간으로 계산해보세요"
},
"cta": {
"title": "프로젝트 견적을 확인해보세요",
"subtitle": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"button": "견적 계산기 사용하기"
},
"step1": {
"title": "1단계: 서비스 선택",
"subtitle": "필요한 서비스를 선택해주세요 (복수 선택 가능)"
},
"step2": {
"title": "2단계: 프로젝트 세부사항",
"subtitle": "프로젝트의 복잡도와 일정을 선택해주세요"
},
"complexity": {
"title": "프로젝트 복잡도",
"simple": "단순",
"simple_desc": "기본 기능, 표준 디자인",
"medium": "보통",
"medium_desc": "추가 기능, 커스텀 디자인",
"complex": "복잡",
"complex_desc": "고급 기능, 복합 통합"
},
"timeline": {
"title": "개발 일정",
"standard": "표준",
"standard_desc": "일반적인 개발 기간",
"rush": "급한",
"rush_desc": "빠른 개발 (+50%)",
"extended": "여유",
"extended_desc": "넉넉한 개발 기간 (-20%)"
},
"result": {
"title": "견적 결과",
"subtitle": "프로젝트 예상 견적입니다",
"estimated_price": "예상 견적",
"price_note": "* 최종 견적은 프로젝트 세부사항에 따라 달라질 수 있습니다",
"summary": "프로젝트 요약",
"selected_services": "선택한 서비스",
"complexity": "복잡도",
"timeline": "일정",
"get_quote": "정확한 견적 받기",
"recalculate": "다시 계산",
"contact_note": "정확한 견적과 프로젝트 상담을 위해 연락주세요"
},
"next_step": "다음 단계",
"prev_step": "이전",
"calculate": "계산하기"
},
"nav": {
"home": "홈",
"about": "회사소개",
"services": "서비스",
"portfolio": "포트폴리오",
"contact": "연락처",
"calculator": "견적계산기",
"admin": "관리자"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다",
"cta_primary": "프로젝트 시작하기",
"cta_secondary": "포트폴리오 보기"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션",
"web_development": {
"title": "웹 개발",
"description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발",
"price": "₩500,000~"
},
"mobile_app": {
"title": "모바일 앱",
"description": "iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱",
"price": "₩800,000~"
},
"ui_ux_design": {
"title": "UI/UX 디자인",
"description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인",
"price": "₩300,000~"
},
"digital_marketing": {
"title": "디지털 마케팅",
"description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅",
"price": "₩200,000~"
},
"view_all": "모든 서비스 보기"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "고객의 성공을 위해 완성한 프로젝트들을 확인해보세요",
"view_details": "자세히 보기",
"view_all": "전체 포트폴리오 보기"
},
"calculator": {
"title": "프로젝트 견적을 확인해보세요",
"description": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"cta": "견적 계산기 사용하기"
},
"contact": {
"ready_title": "프로젝트를 시작할 준비가 되셨나요?",
"ready_description": "아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다.",
"phone_consultation": "전화 상담",
"email_inquiry": "이메일 문의",
"telegram_chat": "텔레그램 채팅",
"instant_response": "즉시 답변 가능",
"free_consultation": "무료 상담 신청",
"form": {
"name": "이름",
"email": "이메일",
"phone": "연락처",
"service_interest": "관심 서비스",
"service_options": {
"select": "관심 서비스 선택",
"web_development": "웹 개발",
"mobile_app": "모바일 앱",
"ui_ux_design": "UI/UX 디자인",
"branding": "브랜딩",
"consulting": "컨설팅",
"other": "기타"
},
"message": "프로젝트에 대해 간단히 설명해주세요",
"submit": "상담 신청하기"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업",
"overview": {
"title": "혁신과 창의로 만드는 미래",
"description_1": "SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다.",
"description_2": "우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 제안하여 함께 성장하는 파트너가 되고자 합니다.",
"stats": {
"projects": "100+",
"projects_label": "완료 프로젝트",
"clients": "50+",
"clients_label": "만족한 고객",
"experience": "4년",
"experience_label": "업계 경험"
},
"mission": "우리의 미션",
"mission_text": "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것",
"vision": "우리의 비전",
"vision_text": "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 전 세계 고객들의 디지털 혁신을 주도하는 것"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "SmartSolTech가 추구하는 핵심 가치들",
"innovation": {
"title": "혁신 (Innovation)",
"description": "끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다."
},
"collaboration": {
"title": "협력 (Collaboration)",
"description": "고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다."
},
"quality": {
"title": "품질 (Quality)",
"description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다."
},
"growth": {
"title": "성장 (Growth)",
"description": "고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "함께 성공하는 파트너가 되어보세요",
"description": "SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요",
"partnership": "파트너십 문의",
"portfolio": "포트폴리오 보기"
}
},
"footer": {
"company": "SmartSolTech",
"description": "혁신을 이끌어가는 디지털 솔루션 전문 기업",
"quick_links": "빠른 링크",
"services": "서비스",
"contact_info": "연락처 정보",
"follow_us": "팔로우하기",
"rights": "모든 권리 보유."
},
"theme": {
"light": "라이트 테마",
"dark": "다크 테마",
"toggle": "테마 전환"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "로딩 중...",
"error": "오류가 발생했습니다",
"success": "성공",
"view_more": "더 보기",
"back": "뒤로",
"next": "다음",
"previous": "이전"
}
}

View File

@@ -0,0 +1,230 @@
{
"navigation": {
"home": "홈",
"about": "calculator": {
"title": "프로젝트 견적 계산기",
"subtitle": "원하는 서비스와 요구사항을 선택하면 실시간으로 정확한 견적을 계산해드립니다",
"meta": {
"title": "프로젝트 견적 계산기",
"description": "웹 개발, 모바일 앱, 디자인 프로젝트 비용을 실시간으로 계산해보세요"
},
"cta": {
"title": "프로젝트 견적을 확인해보세요",
"subtitle": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"button": "견적 계산기 사용하기"
},
"step1": {
"title": "1단계: 서비스 선택",
"subtitle": "필요한 서비스를 선택해주세요 (복수 선택 가능)"
},
"step2": {
"title": "2단계: 프로젝트 세부사항",
"subtitle": "프로젝트의 복잡도와 일정을 선택해주세요"
},
"complexity": {
"title": "프로젝트 복잡도",
"simple": "단순",
"simple_desc": "기본 기능, 표준 디자인",
"medium": "보통",
"medium_desc": "추가 기능, 커스텀 디자인",
"complex": "복잡",
"complex_desc": "고급 기능, 복합 통합"
},
"timeline": {
"title": "개발 일정",
"standard": "표준",
"standard_desc": "일반적인 개발 기간",
"rush": "급한",
"rush_desc": "빠른 개발 (+50%)",
"extended": "여유",
"extended_desc": "넉넉한 개발 기간 (-20%)"
},
"result": {
"title": "견적 결과",
"subtitle": "프로젝트 예상 견적입니다",
"estimated_price": "예상 견적",
"price_note": "* 최종 견적은 프로젝트 세부사항에 따라 달라질 수 있습니다",
"summary": "프로젝트 요약",
"selected_services": "선택한 서비스",
"complexity": "복잡도",
"timeline": "일정",
"get_quote": "정확한 견적 받기",
"recalculate": "다시 계산",
"contact_note": "정확한 견적과 프로젝트 상담을 위해 연락주세요"
},
"next_step": "다음 단계",
"prev_step": "이전",
"calculate": "계산하기"
},
"nav": {
"home": "홈",
"about": "회사소개",
"services": "서비스",
"portfolio": "포트폴리오",
"contact": "연락처",
"calculator": "견적계산기",
"admin": "관리자"
},
"hero": {
"title": "Smart Technology",
"subtitle": "Solutions",
"description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다",
"cta_primary": "프로젝트 시작하기",
"cta_secondary": "포트폴리오 보기"
},
"services": {
"title": "Our",
"title_highlight": "Services",
"description": "최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션",
"web_development": {
"title": "웹 개발",
"description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발",
"price": "₩500,000~"
},
"mobile_app": {
"title": "모바일 앱",
"description": "iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱",
"price": "₩800,000~"
},
"ui_ux_design": {
"title": "UI/UX 디자인",
"description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인",
"price": "₩300,000~"
},
"digital_marketing": {
"title": "디지털 마케팅",
"description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅",
"price": "₩200,000~"
},
"view_all": "모든 서비스 보기"
},
"portfolio": {
"title": "Recent",
"title_highlight": "Projects",
"description": "고객의 성공을 위해 완성한 프로젝트들을 확인해보세요",
"view_details": "자세히 보기",
"view_all": "전체 포트폴리오 보기"
},
"calculator": {
"title": "프로젝트 견적을 확인해보세요",
"description": "원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다",
"cta": "견적 계산기 사용하기"
},
"contact": {
"ready_title": "프로젝트를 시작할 준비가 되셨나요?",
"ready_description": "아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다.",
"phone_consultation": "전화 상담",
"email_inquiry": "이메일 문의",
"telegram_chat": "텔레그램 채팅",
"instant_response": "즉시 답변 가능",
"free_consultation": "무료 상담 신청",
"form": {
"name": "이름",
"email": "이메일",
"phone": "연락처",
"service_interest": "관심 서비스",
"service_options": {
"select": "관심 서비스 선택",
"web_development": "웹 개발",
"mobile_app": "모바일 앱",
"ui_ux_design": "UI/UX 디자인",
"branding": "브랜딩",
"consulting": "컨설팅",
"other": "기타"
},
"message": "프로젝트에 대해 간단히 설명해주세요",
"submit": "상담 신청하기"
}
},
"about": {
"hero_title": "About",
"hero_highlight": "SmartSolTech",
"hero_description": "혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업",
"overview": {
"title": "혁신과 창의로 만드는 미래",
"description_1": "SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다.",
"description_2": "우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 제안하여 함께 성장하는 파트너가 되고자 합니다.",
"stats": {
"projects": "100+",
"projects_label": "완료 프로젝트",
"clients": "50+",
"clients_label": "만족한 고객",
"experience": "4년",
"experience_label": "업계 경험"
},
"mission": "우리의 미션",
"mission_text": "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것",
"vision": "우리의 비전",
"vision_text": "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 전 세계 고객들의 디지털 혁신을 주도하는 것"
},
"values": {
"title": "Core",
"title_highlight": "Values",
"description": "SmartSolTech가 추구하는 핵심 가치들",
"innovation": {
"title": "혁신 (Innovation)",
"description": "끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다."
},
"collaboration": {
"title": "협력 (Collaboration)",
"description": "고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다."
},
"quality": {
"title": "품질 (Quality)",
"description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다."
},
"growth": {
"title": "성장 (Growth)",
"description": "고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다."
}
},
"team": {
"title": "Our",
"title_highlight": "Team",
"description": "전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다"
},
"tech_stack": {
"title": "Technology",
"title_highlight": "Stack",
"description": "최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Mobile"
},
"cta": {
"title": "함께 성공하는 파트너가 되어보세요",
"description": "SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요",
"partnership": "파트너십 문의",
"portfolio": "포트폴리오 보기"
}
},
"footer": {
"company": "SmartSolTech",
"description": "혁신을 이끌어가는 디지털 솔루션 전문 기업",
"quick_links": "빠른 링크",
"services": "서비스",
"contact_info": "연락처 정보",
"follow_us": "팔로우하기",
"rights": "모든 권리 보유."
},
"theme": {
"light": "라이트 테마",
"dark": "다크 테마",
"toggle": "테마 전환"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "로딩 중...",
"error": "오류가 발생했습니다",
"success": "성공",
"view_more": "더 보기",
"back": "뒤로",
"next": "다음",
"previous": "이전"
}
}

View File

@@ -0,0 +1,196 @@
{
"undefined - SmartSolTech": "undefined - SmartSolTech",
"meta": {
"description": "meta.description",
"keywords": "meta.keywords",
"title": "meta.title"
},
"navigation": {
"home": "navigation.home",
"about": "navigation.about",
"services": "navigation.services",
"portfolio": "navigation.portfolio",
"calculator": "navigation.calculator",
"contact": "navigation.contact",
"home - SmartSolTech": "navigation.home - SmartSolTech"
},
"language": {
"ko": "language.ko",
"korean": "language.korean",
"english": "language.english",
"russian": "language.russian",
"kazakh": "language.kazakh"
},
"theme": {
"toggle": "theme.toggle"
},
"hero": {
"cta_primary": "hero.cta_primary",
"title": {
"smart": "hero.title.smart",
"solutions": "hero.title.solutions"
},
"subtitle": "hero.subtitle",
"cta": {
"start": "hero.cta.start",
"portfolio": "hero.cta.portfolio"
}
},
"services": {
"title": {
"our": "services.title.our",
"services": "services.title.services"
},
"subtitle": "services.subtitle",
"web": {
"title": "services.web.title",
"description": "services.web.description",
"price": "services.web.price"
},
"mobile": {
"title": "services.mobile.title",
"description": "services.mobile.description",
"price": "services.mobile.price"
},
"design": {
"title": "services.design.title",
"description": "services.design.description",
"price": "services.design.price"
},
"marketing": {
"title": "services.marketing.title",
"description": "services.marketing.description",
"price": "services.marketing.price"
},
"view_all": "services.view_all"
},
"portfolio": {
"title": {
"recent": "portfolio.title.recent",
"projects": "portfolio.title.projects"
},
"subtitle": "portfolio.subtitle",
"view_all": "portfolio.view_all"
},
"common": {
"view_details": "common.view_details"
},
"calculator": {
"cta": {
"title": "calculator.cta.title",
"subtitle": "calculator.cta.subtitle",
"button": "calculator.cta.button"
},
"meta": {
"title": "calculator.meta.title",
"description": "calculator.meta.description"
},
"title": "calculator.title",
"subtitle": "calculator.subtitle",
"step1": {
"title": "calculator.step1.title",
"subtitle": "calculator.step1.subtitle"
},
"next_step": "calculator.next_step",
"step2": {
"title": "calculator.step2.title",
"subtitle": "calculator.step2.subtitle"
},
"complexity": {
"title": "calculator.complexity.title",
"simple": "calculator.complexity.simple",
"simple_desc": "calculator.complexity.simple_desc",
"medium": "calculator.complexity.medium",
"medium_desc": "calculator.complexity.medium_desc",
"complex": "calculator.complexity.complex",
"complex_desc": "calculator.complexity.complex_desc"
},
"timeline": {
"title": "calculator.timeline.title",
"standard": "calculator.timeline.standard",
"standard_desc": "calculator.timeline.standard_desc",
"rush": "calculator.timeline.rush",
"rush_desc": "calculator.timeline.rush_desc",
"extended": "calculator.timeline.extended",
"extended_desc": "calculator.timeline.extended_desc"
},
"prev_step": "calculator.prev_step",
"calculate": "calculator.calculate",
"result": {
"title": "calculator.result.title",
"subtitle": "calculator.result.subtitle",
"estimated_price": "calculator.result.estimated_price",
"price_note": "calculator.result.price_note",
"summary": "calculator.result.summary",
"get_quote": "calculator.result.get_quote",
"recalculate": "calculator.result.recalculate",
"contact_note": "calculator.result.contact_note",
"selected_services": "선택된 서비스",
"complexity": "복잡도",
"timeline": "개발 기간"
}
},
"contact": {
"cta": {
"ready": "contact.cta.ready",
"start": "contact.cta.start",
"question": "contact.cta.question",
"subtitle": "contact.cta.subtitle"
},
"phone": {
"title": "contact.phone.title",
"number": "contact.phone.number"
},
"email": {
"title": "contact.email.title",
"address": "contact.email.address"
},
"telegram": {
"title": "contact.telegram.title",
"subtitle": "contact.telegram.subtitle"
},
"form": {
"title": "contact.form.title",
"name": "contact.form.name",
"email": "contact.form.email",
"phone": "contact.form.phone",
"service": {
"select": "contact.form.service.select",
"web": "contact.form.service.web",
"mobile": "contact.form.service.mobile",
"design": "contact.form.service.design",
"branding": "contact.form.service.branding",
"consulting": "contact.form.service.consulting",
"other": "contact.form.service.other"
},
"message": "contact.form.message",
"submit": "contact.form.submit",
"success": "contact.form.success",
"error": "contact.form.error"
}
},
"footer": {
"company": {
"description": "footer.company.description"
},
"links": {
"title": "footer.links.title"
},
"contact": {
"title": "footer.contact.title",
"email": "footer.contact.email",
"phone": "footer.contact.phone",
"address": "footer.contact.address"
},
"copyright": "footer.copyright",
"privacy": "footer.privacy",
"terms": "footer.terms"
},
"nav": {
"home": "nav.home",
"about": "nav.about",
"services": "nav.services",
"portfolio": "nav.portfolio",
"calculator": "nav.calculator"
}
}

View File

@@ -0,0 +1,196 @@
{
"undefined - SmartSolTech": "undefined - SmartSolTech",
"meta": {
"description": "meta.description",
"keywords": "meta.keywords",
"title": "meta.title"
},
"navigation": {
"home": "navigation.home",
"about": "navigation.about",
"services": "navigation.services",
"portfolio": "navigation.portfolio",
"calculator": "navigation.calculator",
"contact": "navigation.contact",
"home - SmartSolTech": "navigation.home - SmartSolTech"
},
"language": {
"ko": "language.ko",
"korean": "language.korean",
"english": "language.english",
"russian": "language.russian",
"kazakh": "language.kazakh"
},
"theme": {
"toggle": "theme.toggle"
},
"hero": {
"cta_primary": "hero.cta_primary",
"title": {
"smart": "hero.title.smart",
"solutions": "hero.title.solutions"
},
"subtitle": "hero.subtitle",
"cta": {
"start": "hero.cta.start",
"portfolio": "hero.cta.portfolio"
}
},
"services": {
"title": {
"our": "services.title.our",
"services": "services.title.services"
},
"subtitle": "services.subtitle",
"web": {
"title": "services.web.title",
"description": "services.web.description",
"price": "services.web.price"
},
"mobile": {
"title": "services.mobile.title",
"description": "services.mobile.description",
"price": "services.mobile.price"
},
"design": {
"title": "services.design.title",
"description": "services.design.description",
"price": "services.design.price"
},
"marketing": {
"title": "services.marketing.title",
"description": "services.marketing.description",
"price": "services.marketing.price"
},
"view_all": "services.view_all"
},
"portfolio": {
"title": {
"recent": "portfolio.title.recent",
"projects": "portfolio.title.projects"
},
"subtitle": "portfolio.subtitle",
"view_all": "portfolio.view_all"
},
"common": {
"view_details": "common.view_details"
},
"calculator": {
"cta": {
"title": "calculator.cta.title",
"subtitle": "calculator.cta.subtitle",
"button": "calculator.cta.button"
},
"meta": {
"title": "calculator.meta.title",
"description": "calculator.meta.description"
},
"title": "calculator.title",
"subtitle": "calculator.subtitle",
"step1": {
"title": "calculator.step1.title",
"subtitle": "calculator.step1.subtitle"
},
"next_step": "calculator.next_step",
"step2": {
"title": "calculator.step2.title",
"subtitle": "calculator.step2.subtitle"
},
"complexity": {
"title": "calculator.complexity.title",
"simple": "calculator.complexity.simple",
"simple_desc": "calculator.complexity.simple_desc",
"medium": "calculator.complexity.medium",
"medium_desc": "calculator.complexity.medium_desc",
"complex": "calculator.complexity.complex",
"complex_desc": "calculator.complexity.complex_desc"
},
"timeline": {
"title": "calculator.timeline.title",
"standard": "calculator.timeline.standard",
"standard_desc": "calculator.timeline.standard_desc",
"rush": "calculator.timeline.rush",
"rush_desc": "calculator.timeline.rush_desc",
"extended": "calculator.timeline.extended",
"extended_desc": "calculator.timeline.extended_desc"
},
"prev_step": "calculator.prev_step",
"calculate": "calculator.calculate",
"result": {
"title": "calculator.result.title",
"subtitle": "calculator.result.subtitle",
"estimated_price": "calculator.result.estimated_price",
"price_note": "calculator.result.price_note",
"summary": "calculator.result.summary",
"get_quote": "calculator.result.get_quote",
"recalculate": "calculator.result.recalculate",
"contact_note": "calculator.result.contact_note",
"selected_services": "선택된 서비스",
"complexity": "복잡도",
"timeline": "개발 기간"
}
},
"contact": {
"cta": {
"ready": "contact.cta.ready",
"start": "contact.cta.start",
"question": "contact.cta.question",
"subtitle": "contact.cta.subtitle"
},
"phone": {
"title": "contact.phone.title",
"number": "contact.phone.number"
},
"email": {
"title": "contact.email.title",
"address": "contact.email.address"
},
"telegram": {
"title": "contact.telegram.title",
"subtitle": "contact.telegram.subtitle"
},
"form": {
"title": "contact.form.title",
"name": "contact.form.name",
"email": "contact.form.email",
"phone": "contact.form.phone",
"service": {
"select": "contact.form.service.select",
"web": "contact.form.service.web",
"mobile": "contact.form.service.mobile",
"design": "contact.form.service.design",
"branding": "contact.form.service.branding",
"consulting": "contact.form.service.consulting",
"other": "contact.form.service.other"
},
"message": "contact.form.message",
"submit": "contact.form.submit",
"success": "contact.form.success",
"error": "contact.form.error"
}
},
"footer": {
"company": {
"description": "footer.company.description"
},
"links": {
"title": "footer.links.title"
},
"contact": {
"title": "footer.contact.title",
"email": "footer.contact.email",
"phone": "footer.contact.phone",
"address": "footer.contact.address"
},
"copyright": "footer.copyright",
"privacy": "footer.privacy",
"terms": "footer.terms"
},
"nav": {
"home": "nav.home",
"about": "nav.about",
"services": "nav.services",
"portfolio": "nav.portfolio",
"calculator": "nav.calculator"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "Главная",
"about": "О нас",
"services": "Услуги",
"portfolio": "Портфолио",
"contact": "Контакты",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Умные Технологические",
"subtitle": "Решения",
"description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса",
"cta_primary": "Начать проект",
"cta_secondary": "Посмотреть портфолио"
},
"services": {
"title": "Наши",
"title_highlight": "Услуги",
"description": "Цифровые решения с использованием передовых технологий и творческих идей",
"web_development": {
"title": "Веб-разработка",
"description": "Современные и адаптивные веб-сайты и веб-приложения",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильные приложения",
"description": "Нативные и кроссплатформенные приложения для iOS и Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифровой маркетинг",
"description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу",
"price": "$2,000~"
},
"view_all": "Посмотреть все услуги"
},
"portfolio": {
"title": "Недавние",
"title_highlight": "Проекты",
"description": "Ознакомьтесь с проектами, выполненными для успеха клиентов",
"view_details": "Подробнее",
"view_all": "Посмотреть все портфолио"
},
"calculator": {
"title": "Проверьте стоимость вашего проекта",
"description": "Выберите нужные услуги и требования для расчета стоимости в реальном времени",
"cta": "Использовать калькулятор стоимости"
},
"contact": {
"ready_title": "Готовы начать свой проект?",
"ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.",
"phone_consultation": "Телефонная консультация",
"email_inquiry": "Запрос по электронной почте",
"telegram_chat": "Чат в Telegram",
"instant_response": "Мгновенный ответ доступен",
"free_consultation": "Заявка на бесплатную консультацию",
"form": {
"name": "Имя",
"email": "Электронная почта",
"phone": "Телефон",
"service_interest": "Интересующая услуга",
"service_options": {
"select": "Выберите интересующую услугу",
"web_development": "Веб-разработка",
"mobile_app": "Мобильное приложение",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Консалтинг",
"other": "Другое"
},
"message": "Кратко опишите ваш проект",
"submit": "Подать заявку на консультацию"
}
},
"about": {
"hero_title": "О",
"hero_highlight": "SmartSolTech",
"hero_description": "Специалист по цифровым решениям, ведущий к успеху клиентов с помощью инновационных технологий",
"overview": {
"title": "Создавая будущее с инновациями и креативностью",
"description_1": "SmartSolTech - это специалист по цифровым решениям, основанный в 2020 году, поддерживающий успех клиентского бизнеса с помощью инновационных технологий и творческих идей в области веб-разработки, мобильных приложений и UI/UX дизайна.",
"description_2": "Мы не просто предоставляем технологии, но понимаем цели клиентов и предлагаем оптимальные решения, чтобы стать партнерами, растущими вместе.",
"stats": {
"projects": "100+",
"projects_label": "Завершенные проекты",
"clients": "50+",
"clients_label": "Довольные клиенты",
"experience": "4 года",
"experience_label": "Опыт в отрасли"
},
"mission": "Наша миссия",
"mission_text": "Помощь всем предприятиям в достижении успеха в цифровую эпоху с помощью технологий",
"vision": "Наше видение",
"vision_text": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру"
},
"values": {
"title": "Основные",
"title_highlight": "Ценности",
"description": "Основные ценности, которых придерживается SmartSolTech",
"innovation": {
"title": "Инновации",
"description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий."
},
"collaboration": {
"title": "Сотрудничество",
"description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами."
},
"quality": {
"title": "Качество",
"description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны."
},
"growth": {
"title": "Рост",
"description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию."
}
},
"team": {
"title": "Наша",
"title_highlight": "Команда",
"description": "Представляем команду SmartSolTech с экспертизой и страстью"
},
"tech_stack": {
"title": "Технологический",
"title_highlight": "Стек",
"description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильные"
},
"cta": {
"title": "Станьте партнером для совместного успеха",
"description": "Выведите свой бизнес на следующий уровень с SmartSolTech",
"partnership": "Запрос о партнерстве",
"portfolio": "Посмотреть портфолио"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Специалист по цифровым решениям, ведущий инновации",
"quick_links": "Быстрые ссылки",
"services": "Услуги",
"contact_info": "Контактная информация",
"follow_us": "Подписывайтесь",
"rights": "Все права защищены."
},
"theme": {
"light": "Светлая тема",
"dark": "Темная тема",
"toggle": "Переключить тему"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Загрузка...",
"error": "Произошла ошибка",
"success": "Успешно",
"view_more": "Посмотреть еще",
"back": "Назад",
"next": "Далее",
"previous": "Предыдущий"
}
}

View File

@@ -0,0 +1,173 @@
{
"navigation": {
"home": "Главная",
"about": "О нас",
"services": "Услуги",
"portfolio": "Портфолио",
"contact": "Контакты",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Умные Технологические",
"subtitle": "Решения",
"description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса",
"cta_primary": "Начать проект",
"cta_secondary": "Посмотреть портфолио"
},
"services": {
"title": "Наши",
"title_highlight": "Услуги",
"description": "Цифровые решения с использованием передовых технологий и творческих идей",
"web_development": {
"title": "Веб-разработка",
"description": "Современные и адаптивные веб-сайты и веб-приложения",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильные приложения",
"description": "Нативные и кроссплатформенные приложения для iOS и Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифровой маркетинг",
"description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу",
"price": "$2,000~"
},
"view_all": "Посмотреть все услуги"
},
"portfolio": {
"title": "Недавние",
"title_highlight": "Проекты",
"description": "Ознакомьтесь с проектами, выполненными для успеха клиентов",
"view_details": "Подробнее",
"view_all": "Посмотреть все портфолио"
},
"calculator": {
"title": "Проверьте стоимость вашего проекта",
"description": "Выберите нужные услуги и требования для расчета стоимости в реальном времени",
"cta": "Использовать калькулятор стоимости"
},
"contact": {
"ready_title": "Готовы начать свой проект?",
"ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.",
"phone_consultation": "Телефонная консультация",
"email_inquiry": "Запрос по электронной почте",
"telegram_chat": "Чат в Telegram",
"instant_response": "Мгновенный ответ доступен",
"free_consultation": "Заявка на бесплатную консультацию",
"form": {
"name": "Имя",
"email": "Электронная почта",
"phone": "Телефон",
"service_interest": "Интересующая услуга",
"service_options": {
"select": "Выберите интересующую услугу",
"web_development": "Веб-разработка",
"mobile_app": "Мобильное приложение",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Консалтинг",
"other": "Другое"
},
"message": "Кратко опишите ваш проект",
"submit": "Подать заявку на консультацию"
}
},
"about": {
"hero_title": "О",
"hero_highlight": "SmartSolTech",
"hero_description": "Специалист по цифровым решениям, ведущий к успеху клиентов с помощью инновационных технологий",
"overview": {
"title": "Создавая будущее с инновациями и креативностью",
"description_1": "SmartSolTech - это специалист по цифровым решениям, основанный в 2020 году, поддерживающий успех клиентского бизнеса с помощью инновационных технологий и творческих идей в области веб-разработки, мобильных приложений и UI/UX дизайна.",
"description_2": "Мы не просто предоставляем технологии, но понимаем цели клиентов и предлагаем оптимальные решения, чтобы стать партнерами, растущими вместе.",
"stats": {
"projects": "100+",
"projects_label": "Завершенные проекты",
"clients": "50+",
"clients_label": "Довольные клиенты",
"experience": "4 года",
"experience_label": "Опыт в отрасли"
},
"mission": "Наша миссия",
"mission_text": "Помощь всем предприятиям в достижении успеха в цифровую эпоху с помощью технологий",
"vision": "Наше видение",
"vision_text": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру"
},
"values": {
"title": "Основные",
"title_highlight": "Ценности",
"description": "Основные ценности, которых придерживается SmartSolTech",
"innovation": {
"title": "Инновации",
"description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий."
},
"collaboration": {
"title": "Сотрудничество",
"description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами."
},
"quality": {
"title": "Качество",
"description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны."
},
"growth": {
"title": "Рост",
"description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию."
}
},
"team": {
"title": "Наша",
"title_highlight": "Команда",
"description": "Представляем команду SmartSolTech с экспертизой и страстью"
},
"tech_stack": {
"title": "Технологический",
"title_highlight": "Стек",
"description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильные"
},
"cta": {
"title": "Станьте партнером для совместного успеха",
"description": "Выведите свой бизнес на следующий уровень с SmartSolTech",
"partnership": "Запрос о партнерстве",
"portfolio": "Посмотреть портфолио"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Специалист по цифровым решениям, ведущий инновации",
"quick_links": "Быстрые ссылки",
"services": "Услуги",
"contact_info": "Контактная информация",
"follow_us": "Подписывайтесь",
"rights": "Все права защищены."
},
"theme": {
"light": "Светлая тема",
"dark": "Темная тема",
"toggle": "Переключить тему"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Загрузка...",
"error": "Произошла ошибка",
"success": "Успешно",
"view_more": "Посмотреть еще",
"back": "Назад",
"next": "Далее",
"previous": "Предыдущий"
}
}

View File

@@ -0,0 +1,223 @@
{
"navigation": {
"home": "Главная",
"about": "О нас",
"services": "Услуги",
"portfolio": "Портфолио",
"contact": "Контакты",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Умные Технологические",
"subtitle": "Решения",
"description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса",
"cta_primary": "Начать проект",
"cta_secondary": "Посмотреть портфолио"
},
"services": {
"title": "Наши",
"title_highlight": "Услуги",
"description": "Цифровые решения с использованием передовых технологий и творческих идей",
"web_development": {
"title": "Веб-разработка",
"description": "Современные и адаптивные веб-сайты и веб-приложения",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильные приложения",
"description": "Нативные и кроссплатформенные приложения для iOS и Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифровой маркетинг",
"description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу",
"price": "$2,000~"
},
"view_all": "Посмотреть все услуги"
},
"portfolio": {
"title": "Недавние",
"title_highlight": "Проекты",
"description": "Ознакомьтесь с проектами, выполненными для успеха клиентов",
"view_details": "Подробнее",
"view_all": "Посмотреть все портфолио"
},
"calculator": {
"title": "Калькулятор Стоимости Проекта",
"subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в режиме реального времени",
"meta": {
"title": "Калькулятор стоимости проекта",
"description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора"
},
"cta": {
"title": "Узнайте стоимость вашего проекта",
"subtitle": "Выберите необходимые услуги и требования, и мы рассчитаем стоимость в режиме реального времени",
"button": "Использовать калькулятор стоимости"
},
"step1": {
"title": "Шаг 1: Выбор услуг",
"subtitle": "Выберите необходимые услуги (можно выбрать несколько)"
},
"step2": {
"title": "Шаг 2: Детали проекта",
"subtitle": "Выберите сложность проекта и сроки"
},
"complexity": {
"title": "Сложность проекта",
"simple": "Простой",
"simple_desc": "Базовый функционал, стандартный дизайн",
"medium": "Средний",
"medium_desc": "Дополнительные функции, кастомный дизайн",
"complex": "Сложный",
"complex_desc": "Расширенный функционал, интеграции"
},
"timeline": {
"title": "Временные рамки",
"standard": "Стандартные",
"standard_desc": "Обычные сроки разработки",
"rush": "Срочно",
"rush_desc": "Ускоренная разработка (+50%)",
"extended": "Расширенные",
"extended_desc": "Длительная разработка (-20%)"
},
"result": {
"title": "Результат расчета",
"subtitle": "Вот ваша предварительная оценка стоимости проекта",
"estimated_price": "Предварительная стоимость",
"price_note": "* Окончательная стоимость может варьироваться в зависимости от деталей проекта",
"summary": "Сводка проекта",
"selected_services": "Выбранные услуги",
"complexity": "Сложность",
"timeline": "Временные рамки",
"get_quote": "Получить точное предложение",
"recalculate": "Пересчитать",
"contact_note": "Свяжитесь с нами для получения точного предложения и обсуждения деталей проекта"
},
"next_step": "Следующий шаг",
"prev_step": "Назад",
"calculate": "Рассчитать"
},
"contact": {
"ready_title": "Готовы начать свой проект?",
"ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.",
"phone_consultation": "Телефонная консультация",
"email_inquiry": "Запрос по электронной почте",
"telegram_chat": "Чат в Telegram",
"instant_response": "Мгновенный ответ доступен",
"free_consultation": "Заявка на бесплатную консультацию",
"form": {
"name": "Имя",
"email": "Электронная почта",
"phone": "Телефон",
"service_interest": "Интересующая услуга",
"service_options": {
"select": "Выберите интересующую услугу",
"web_development": "Веб-разработка",
"mobile_app": "Мобильное приложение",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Консалтинг",
"other": "Другое"
},
"message": "Кратко опишите ваш проект",
"submit": "Подать заявку на консультацию"
}
},
"about": {
"hero_title": "О",
"hero_highlight": "SmartSolTech",
"hero_description": "Специалист по цифровым решениям, ведущий к успеху клиентов с помощью инновационных технологий",
"overview": {
"title": "Создавая будущее с инновациями и креативностью",
"description_1": "SmartSolTech - это специалист по цифровым решениям, основанный в 2020 году, поддерживающий успех клиентского бизнеса с помощью инновационных технологий и творческих идей в области веб-разработки, мобильных приложений и UI/UX дизайна.",
"description_2": "Мы не просто предоставляем технологии, но понимаем цели клиентов и предлагаем оптимальные решения, чтобы стать партнерами, растущими вместе.",
"stats": {
"projects": "100+",
"projects_label": "Завершенные проекты",
"clients": "50+",
"clients_label": "Довольные клиенты",
"experience": "4 года",
"experience_label": "Опыт в отрасли"
},
"mission": "Наша миссия",
"mission_text": "Помощь всем предприятиям в достижении успеха в цифровую эпоху с помощью технологий",
"vision": "Наше видение",
"vision_text": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру"
},
"values": {
"title": "Основные",
"title_highlight": "Ценности",
"description": "Основные ценности, которых придерживается SmartSolTech",
"innovation": {
"title": "Инновации",
"description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий."
},
"collaboration": {
"title": "Сотрудничество",
"description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами."
},
"quality": {
"title": "Качество",
"description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны."
},
"growth": {
"title": "Рост",
"description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию."
}
},
"team": {
"title": "Наша",
"title_highlight": "Команда",
"description": "Представляем команду SmartSolTech с экспертизой и страстью"
},
"tech_stack": {
"title": "Технологический",
"title_highlight": "Стек",
"description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильные"
},
"cta": {
"title": "Станьте партнером для совместного успеха",
"description": "Выведите свой бизнес на следующий уровень с SmartSolTech",
"partnership": "Запрос о партнерстве",
"portfolio": "Посмотреть портфолио"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Специалист по цифровым решениям, ведущий инновации",
"quick_links": "Быстрые ссылки",
"services": "Услуги",
"contact_info": "Контактная информация",
"follow_us": "Подписывайтесь",
"rights": "Все права защищены."
},
"theme": {
"light": "Светлая тема",
"dark": "Темная тема",
"toggle": "Переключить тему"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Загрузка...",
"error": "Произошла ошибка",
"success": "Успешно",
"view_more": "Посмотреть еще",
"back": "Назад",
"next": "Далее",
"previous": "Предыдущий"
}
}

View File

@@ -0,0 +1,223 @@
{
"navigation": {
"home": "Главная",
"about": "О нас",
"services": "Услуги",
"portfolio": "Портфолио",
"contact": "Контакты",
"calculator": "Калькулятор",
"admin": "Админ"
},
"hero": {
"title": "Умные Технологические",
"subtitle": "Решения",
"description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса",
"cta_primary": "Начать проект",
"cta_secondary": "Посмотреть портфолио"
},
"services": {
"title": "Наши",
"title_highlight": "Услуги",
"description": "Цифровые решения с использованием передовых технологий и творческих идей",
"web_development": {
"title": "Веб-разработка",
"description": "Современные и адаптивные веб-сайты и веб-приложения",
"price": "$5,000~"
},
"mobile_app": {
"title": "Мобильные приложения",
"description": "Нативные и кроссплатформенные приложения для iOS и Android",
"price": "$8,000~"
},
"ui_ux_design": {
"title": "UI/UX дизайн",
"description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса",
"price": "$3,000~"
},
"digital_marketing": {
"title": "Цифровой маркетинг",
"description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу",
"price": "$2,000~"
},
"view_all": "Посмотреть все услуги"
},
"portfolio": {
"title": "Недавние",
"title_highlight": "Проекты",
"description": "Ознакомьтесь с проектами, выполненными для успеха клиентов",
"view_details": "Подробнее",
"view_all": "Посмотреть все портфолио"
},
"calculator": {
"title": "Калькулятор Стоимости Проекта",
"subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в режиме реального времени",
"meta": {
"title": "Калькулятор стоимости проекта",
"description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора"
},
"cta": {
"title": "Узнайте стоимость вашего проекта",
"subtitle": "Выберите необходимые услуги и требования, и мы рассчитаем стоимость в режиме реального времени",
"button": "Использовать калькулятор стоимости"
},
"step1": {
"title": "Шаг 1: Выбор услуг",
"subtitle": "Выберите необходимые услуги (можно выбрать несколько)"
},
"step2": {
"title": "Шаг 2: Детали проекта",
"subtitle": "Выберите сложность проекта и сроки"
},
"complexity": {
"title": "Сложность проекта",
"simple": "Простой",
"simple_desc": "Базовый функционал, стандартный дизайн",
"medium": "Средний",
"medium_desc": "Дополнительные функции, кастомный дизайн",
"complex": "Сложный",
"complex_desc": "Расширенный функционал, интеграции"
},
"timeline": {
"title": "Временные рамки",
"standard": "Стандартные",
"standard_desc": "Обычные сроки разработки",
"rush": "Срочно",
"rush_desc": "Ускоренная разработка (+50%)",
"extended": "Расширенные",
"extended_desc": "Длительная разработка (-20%)"
},
"result": {
"title": "Результат расчета",
"subtitle": "Вот ваша предварительная оценка стоимости проекта",
"estimated_price": "Предварительная стоимость",
"price_note": "* Окончательная стоимость может варьироваться в зависимости от деталей проекта",
"summary": "Сводка проекта",
"selected_services": "Выбранные услуги",
"complexity": "Сложность",
"timeline": "Временные рамки",
"get_quote": "Получить точное предложение",
"recalculate": "Пересчитать",
"contact_note": "Свяжитесь с нами для получения точного предложения и обсуждения деталей проекта"
},
"next_step": "Следующий шаг",
"prev_step": "Назад",
"calculate": "Рассчитать"
},
"contact": {
"ready_title": "Готовы начать свой проект?",
"ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.",
"phone_consultation": "Телефонная консультация",
"email_inquiry": "Запрос по электронной почте",
"telegram_chat": "Чат в Telegram",
"instant_response": "Мгновенный ответ доступен",
"free_consultation": "Заявка на бесплатную консультацию",
"form": {
"name": "Имя",
"email": "Электронная почта",
"phone": "Телефон",
"service_interest": "Интересующая услуга",
"service_options": {
"select": "Выберите интересующую услугу",
"web_development": "Веб-разработка",
"mobile_app": "Мобильное приложение",
"ui_ux_design": "UI/UX дизайн",
"branding": "Брендинг",
"consulting": "Консалтинг",
"other": "Другое"
},
"message": "Кратко опишите ваш проект",
"submit": "Подать заявку на консультацию"
}
},
"about": {
"hero_title": "О",
"hero_highlight": "SmartSolTech",
"hero_description": "Специалист по цифровым решениям, ведущий к успеху клиентов с помощью инновационных технологий",
"overview": {
"title": "Создавая будущее с инновациями и креативностью",
"description_1": "SmartSolTech - это специалист по цифровым решениям, основанный в 2020 году, поддерживающий успех клиентского бизнеса с помощью инновационных технологий и творческих идей в области веб-разработки, мобильных приложений и UI/UX дизайна.",
"description_2": "Мы не просто предоставляем технологии, но понимаем цели клиентов и предлагаем оптимальные решения, чтобы стать партнерами, растущими вместе.",
"stats": {
"projects": "100+",
"projects_label": "Завершенные проекты",
"clients": "50+",
"clients_label": "Довольные клиенты",
"experience": "4 года",
"experience_label": "Опыт в отрасли"
},
"mission": "Наша миссия",
"mission_text": "Помощь всем предприятиям в достижении успеха в цифровую эпоху с помощью технологий",
"vision": "Наше видение",
"vision_text": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру"
},
"values": {
"title": "Основные",
"title_highlight": "Ценности",
"description": "Основные ценности, которых придерживается SmartSolTech",
"innovation": {
"title": "Инновации",
"description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий."
},
"collaboration": {
"title": "Сотрудничество",
"description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами."
},
"quality": {
"title": "Качество",
"description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны."
},
"growth": {
"title": "Рост",
"description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию."
}
},
"team": {
"title": "Наша",
"title_highlight": "Команда",
"description": "Представляем команду SmartSolTech с экспертизой и страстью"
},
"tech_stack": {
"title": "Технологический",
"title_highlight": "Стек",
"description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами",
"frontend": "Frontend",
"backend": "Backend",
"mobile": "Мобильные"
},
"cta": {
"title": "Станьте партнером для совместного успеха",
"description": "Выведите свой бизнес на следующий уровень с SmartSolTech",
"partnership": "Запрос о партнерстве",
"portfolio": "Посмотреть портфолио"
}
},
"footer": {
"company": "SmartSolTech",
"description": "Специалист по цифровым решениям, ведущий инновации",
"quick_links": "Быстрые ссылки",
"services": "Услуги",
"contact_info": "Контактная информация",
"follow_us": "Подписывайтесь",
"rights": "Все права защищены."
},
"theme": {
"light": "Светлая тема",
"dark": "Темная тема",
"toggle": "Переключить тему"
},
"language": {
"english": "English",
"korean": "한국어",
"russian": "Русский",
"kazakh": "Қазақша"
},
"common": {
"loading": "Загрузка...",
"error": "Произошла ошибка",
"success": "Успешно",
"view_more": "Посмотреть еще",
"back": "Назад",
"next": "Далее",
"previous": "Предыдущий"
}
}

View File

@@ -0,0 +1,154 @@
const jwt = require('jsonwebtoken');
const User = require('../models/User');
/**
* Authentication middleware
* Verifies JWT token and attaches user to request
*/
const authenticateToken = async (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId).select('-password');
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'Invalid or inactive user'
});
}
req.user = user;
next();
} catch (error) {
console.error('Token verification error:', error);
return res.status(403).json({
success: false,
message: 'Invalid token'
});
}
};
/**
* Session-based authentication middleware
* For web pages using sessions
*/
const authenticateSession = async (req, res, next) => {
try {
if (!req.session.userId) {
req.flash('error', '로그인이 필요합니다.');
return res.redirect('/auth/login');
}
const user = await User.findById(req.session.userId).select('-password');
if (!user || !user.isActive) {
req.session.destroy();
req.flash('error', '유효하지 않은 사용자입니다.');
return res.redirect('/auth/login');
}
req.user = user;
res.locals.user = user;
next();
} catch (error) {
console.error('Session authentication error:', error);
req.session.destroy();
req.flash('error', '인증 오류가 발생했습니다.');
return res.redirect('/auth/login');
}
};
/**
* Admin role middleware
* Requires user to be authenticated and have admin role
*/
const requireAdmin = (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
if (req.user.role !== 'admin') {
return res.status(403).json({
success: false,
message: 'Admin access required'
});
}
next();
};
/**
* Admin session middleware for web pages
*/
const requireAdminSession = (req, res, next) => {
if (!req.user) {
req.flash('error', '로그인이 필요합니다.');
return res.redirect('/auth/login');
}
if (req.user.role !== 'admin') {
req.flash('error', '관리자 권한이 필요합니다.');
return res.redirect('/');
}
next();
};
/**
* Optional authentication middleware
* Attaches user if token exists but doesn't require it
*/
const optionalAuth = async (req, res, next) => {
try {
// Check session first
if (req.session.userId) {
const user = await User.findById(req.session.userId).select('-password');
if (user && user.isActive) {
req.user = user;
res.locals.user = user;
}
}
// Check JWT token if no session
if (!req.user) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId).select('-password');
if (user && user.isActive) {
req.user = user;
res.locals.user = user;
}
}
}
next();
} catch (error) {
// Continue without authentication if token is invalid
next();
}
};
module.exports = {
authenticateToken,
authenticateSession,
requireAdmin,
requireAdminSession,
optionalAuth
};

View File

@@ -0,0 +1,154 @@
const jwt = require('jsonwebtoken');
const User = require('../models/User');
/**
* Authentication middleware
* Verifies JWT token and attaches user to request
*/
const authenticateToken = async (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId).select('-password');
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'Invalid or inactive user'
});
}
req.user = user;
next();
} catch (error) {
console.error('Token verification error:', error);
return res.status(403).json({
success: false,
message: 'Invalid token'
});
}
};
/**
* Session-based authentication middleware
* For web pages using sessions
*/
const authenticateSession = async (req, res, next) => {
try {
if (!req.session.userId) {
req.flash('error', '로그인이 필요합니다.');
return res.redirect('/auth/login');
}
const user = await User.findById(req.session.userId).select('-password');
if (!user || !user.isActive) {
req.session.destroy();
req.flash('error', '유효하지 않은 사용자입니다.');
return res.redirect('/auth/login');
}
req.user = user;
res.locals.user = user;
next();
} catch (error) {
console.error('Session authentication error:', error);
req.session.destroy();
req.flash('error', '인증 오류가 발생했습니다.');
return res.redirect('/auth/login');
}
};
/**
* Admin role middleware
* Requires user to be authenticated and have admin role
*/
const requireAdmin = (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
if (req.user.role !== 'admin') {
return res.status(403).json({
success: false,
message: 'Admin access required'
});
}
next();
};
/**
* Admin session middleware for web pages
*/
const requireAdminSession = (req, res, next) => {
if (!req.user) {
req.flash('error', '로그인이 필요합니다.');
return res.redirect('/auth/login');
}
if (req.user.role !== 'admin') {
req.flash('error', '관리자 권한이 필요합니다.');
return res.redirect('/');
}
next();
};
/**
* Optional authentication middleware
* Attaches user if token exists but doesn't require it
*/
const optionalAuth = async (req, res, next) => {
try {
// Check session first
if (req.session.userId) {
const user = await User.findById(req.session.userId).select('-password');
if (user && user.isActive) {
req.user = user;
res.locals.user = user;
}
}
// Check JWT token if no session
if (!req.user) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId).select('-password');
if (user && user.isActive) {
req.user = user;
res.locals.user = user;
}
}
}
next();
} catch (error) {
// Continue without authentication if token is invalid
next();
}
};
module.exports = {
authenticateToken,
authenticateSession,
requireAdmin,
requireAdminSession,
optionalAuth
};

View File

@@ -0,0 +1,310 @@
/**
* Validation middleware for various data types
*/
const { body, validationResult } = require('express-validator');
/**
* Validation error handler
*/
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
// For API requests
if (req.xhr || req.headers.accept?.includes('application/json')) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
// For web requests
const errorMessages = errors.array().map(error => error.msg);
req.flash('error', errorMessages);
return res.redirect('back');
}
next();
};
/**
* Contact form validation
*/
const validateContactForm = [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('이름은 2-50자 사이여야 합니다.')
.matches(/^[a-zA-Z가-힣\s]+$/)
.withMessage('이름에는 한글, 영문, 공백만 사용할 수 있습니다.'),
body('email')
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.')
.normalizeEmail(),
body('phone')
.optional()
.matches(/^[0-9\-\+\(\)\s]+$/)
.withMessage('유효한 전화번호를 입력해주세요.'),
body('company')
.optional()
.trim()
.isLength({ max: 100 })
.withMessage('회사명은 100자 이하여야 합니다.'),
body('service')
.isIn(['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'digital-marketing', 'consulting', 'other'])
.withMessage('유효한 서비스를 선택해주세요.'),
body('budget')
.optional()
.isIn(['under-500', '500-1000', '1000-3000', '3000-5000', '5000-10000', 'over-10000', 'discuss'])
.withMessage('유효한 예산 범위를 선택해주세요.'),
body('message')
.trim()
.isLength({ min: 10, max: 2000 })
.withMessage('메시지는 10-2000자 사이여야 합니다.'),
handleValidationErrors
];
/**
* User registration validation
*/
const validateRegistration = [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('이름은 2-50자 사이여야 합니다.')
.matches(/^[a-zA-Z가-힣\s]+$/)
.withMessage('이름에는 한글, 영문, 공백만 사용할 수 있습니다.'),
body('email')
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.')
.normalizeEmail(),
body('password')
.isLength({ min: 8 })
.withMessage('비밀번호는 최소 8자 이상이어야 합니다.')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
.withMessage('비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다.'),
body('confirmPassword')
.custom((value, { req }) => {
if (value !== req.body.password) {
throw new Error('비밀번호 확인이 일치하지 않습니다.');
}
return true;
}),
handleValidationErrors
];
/**
* User login validation
*/
const validateLogin = [
body('email')
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.')
.normalizeEmail(),
body('password')
.isLength({ min: 1 })
.withMessage('비밀번호를 입력해주세요.'),
handleValidationErrors
];
/**
* Portfolio validation
*/
const validatePortfolio = [
body('title')
.trim()
.isLength({ min: 2, max: 100 })
.withMessage('제목은 2-100자 사이여야 합니다.'),
body('description')
.trim()
.isLength({ min: 10, max: 5000 })
.withMessage('설명은 10-5000자 사이여야 합니다.'),
body('shortDescription')
.optional()
.trim()
.isLength({ max: 200 })
.withMessage('간단한 설명은 200자 이하여야 합니다.'),
body('category')
.isIn(['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'marketing'])
.withMessage('유효한 카테고리를 선택해주세요.'),
body('technologies')
.optional()
.isArray()
.withMessage('기술 스택은 배열이어야 합니다.'),
body('clientName')
.optional()
.trim()
.isLength({ max: 100 })
.withMessage('클라이언트 이름은 100자 이하여야 합니다.'),
body('projectUrl')
.optional()
.isURL()
.withMessage('유효한 URL을 입력해주세요.'),
body('status')
.optional()
.isIn(['planning', 'in-progress', 'completed', 'on-hold'])
.withMessage('유효한 상태를 선택해주세요.'),
handleValidationErrors
];
/**
* Service validation
*/
const validateService = [
body('name')
.trim()
.isLength({ min: 2, max: 100 })
.withMessage('서비스명은 2-100자 사이여야 합니다.'),
body('description')
.trim()
.isLength({ min: 10, max: 5000 })
.withMessage('설명은 10-5000자 사이여야 합니다.'),
body('shortDescription')
.optional()
.trim()
.isLength({ max: 200 })
.withMessage('간단한 설명은 200자 이하여야 합니다.'),
body('category')
.isIn(['development', 'design', 'marketing', 'consulting'])
.withMessage('유효한 카테고리를 선택해주세요.'),
body('pricing.basePrice')
.optional()
.isNumeric()
.withMessage('기본 가격은 숫자여야 합니다.'),
body('pricing.priceType')
.optional()
.isIn(['project', 'hourly', 'monthly'])
.withMessage('유효한 가격 유형을 선택해주세요.'),
handleValidationErrors
];
/**
* Calculator validation
*/
const validateCalculator = [
body('service')
.isMongoId()
.withMessage('유효한 서비스를 선택해주세요.'),
body('projectType')
.optional()
.isIn(['simple', 'medium', 'complex', 'enterprise'])
.withMessage('유효한 프로젝트 유형을 선택해주세요.'),
body('timeline')
.optional()
.isIn(['urgent', 'normal', 'flexible'])
.withMessage('유효한 타임라인을 선택해주세요.'),
body('features')
.optional()
.isArray()
.withMessage('기능은 배열이어야 합니다.'),
body('contactInfo.name')
.optional()
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('이름은 2-50자 사이여야 합니다.'),
body('contactInfo.email')
.optional()
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.'),
body('contactInfo.phone')
.optional()
.matches(/^[0-9\-\+\(\)\s]+$/)
.withMessage('유효한 전화번호를 입력해주세요.'),
handleValidationErrors
];
/**
* Settings validation
*/
const validateSettings = [
body('siteName')
.optional()
.trim()
.isLength({ min: 1, max: 100 })
.withMessage('사이트명은 1-100자 사이여야 합니다.'),
body('siteDescription')
.optional()
.trim()
.isLength({ max: 500 })
.withMessage('사이트 설명은 500자 이하여야 합니다.'),
body('contact.email')
.optional()
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.'),
body('contact.phone')
.optional()
.matches(/^[0-9\-\+\(\)\s]+$/)
.withMessage('유효한 전화번호를 입력해주세요.'),
body('social.facebook')
.optional()
.isURL()
.withMessage('유효한 Facebook URL을 입력해주세요.'),
body('social.twitter')
.optional()
.isURL()
.withMessage('유효한 Twitter URL을 입력해주세요.'),
body('social.linkedin')
.optional()
.isURL()
.withMessage('유효한 LinkedIn URL을 입력해주세요.'),
body('social.instagram')
.optional()
.isURL()
.withMessage('유효한 Instagram URL을 입력해주세요.'),
handleValidationErrors
];
module.exports = {
handleValidationErrors,
validateContactForm,
validateRegistration,
validateLogin,
validatePortfolio,
validateService,
validateCalculator,
validateSettings
};

View File

@@ -0,0 +1,310 @@
/**
* Validation middleware for various data types
*/
const { body, validationResult } = require('express-validator');
/**
* Validation error handler
*/
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
// For API requests
if (req.xhr || req.headers.accept?.includes('application/json')) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
// For web requests
const errorMessages = errors.array().map(error => error.msg);
req.flash('error', errorMessages);
return res.redirect('back');
}
next();
};
/**
* Contact form validation
*/
const validateContactForm = [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('이름은 2-50자 사이여야 합니다.')
.matches(/^[a-zA-Z가-힣\s]+$/)
.withMessage('이름에는 한글, 영문, 공백만 사용할 수 있습니다.'),
body('email')
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.')
.normalizeEmail(),
body('phone')
.optional()
.matches(/^[0-9\-\+\(\)\s]+$/)
.withMessage('유효한 전화번호를 입력해주세요.'),
body('company')
.optional()
.trim()
.isLength({ max: 100 })
.withMessage('회사명은 100자 이하여야 합니다.'),
body('service')
.isIn(['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'digital-marketing', 'consulting', 'other'])
.withMessage('유효한 서비스를 선택해주세요.'),
body('budget')
.optional()
.isIn(['under-500', '500-1000', '1000-3000', '3000-5000', '5000-10000', 'over-10000', 'discuss'])
.withMessage('유효한 예산 범위를 선택해주세요.'),
body('message')
.trim()
.isLength({ min: 10, max: 2000 })
.withMessage('메시지는 10-2000자 사이여야 합니다.'),
handleValidationErrors
];
/**
* User registration validation
*/
const validateRegistration = [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('이름은 2-50자 사이여야 합니다.')
.matches(/^[a-zA-Z가-힣\s]+$/)
.withMessage('이름에는 한글, 영문, 공백만 사용할 수 있습니다.'),
body('email')
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.')
.normalizeEmail(),
body('password')
.isLength({ min: 8 })
.withMessage('비밀번호는 최소 8자 이상이어야 합니다.')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
.withMessage('비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다.'),
body('confirmPassword')
.custom((value, { req }) => {
if (value !== req.body.password) {
throw new Error('비밀번호 확인이 일치하지 않습니다.');
}
return true;
}),
handleValidationErrors
];
/**
* User login validation
*/
const validateLogin = [
body('email')
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.')
.normalizeEmail(),
body('password')
.isLength({ min: 1 })
.withMessage('비밀번호를 입력해주세요.'),
handleValidationErrors
];
/**
* Portfolio validation
*/
const validatePortfolio = [
body('title')
.trim()
.isLength({ min: 2, max: 100 })
.withMessage('제목은 2-100자 사이여야 합니다.'),
body('description')
.trim()
.isLength({ min: 10, max: 5000 })
.withMessage('설명은 10-5000자 사이여야 합니다.'),
body('shortDescription')
.optional()
.trim()
.isLength({ max: 200 })
.withMessage('간단한 설명은 200자 이하여야 합니다.'),
body('category')
.isIn(['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'marketing'])
.withMessage('유효한 카테고리를 선택해주세요.'),
body('technologies')
.optional()
.isArray()
.withMessage('기술 스택은 배열이어야 합니다.'),
body('clientName')
.optional()
.trim()
.isLength({ max: 100 })
.withMessage('클라이언트 이름은 100자 이하여야 합니다.'),
body('projectUrl')
.optional()
.isURL()
.withMessage('유효한 URL을 입력해주세요.'),
body('status')
.optional()
.isIn(['planning', 'in-progress', 'completed', 'on-hold'])
.withMessage('유효한 상태를 선택해주세요.'),
handleValidationErrors
];
/**
* Service validation
*/
const validateService = [
body('name')
.trim()
.isLength({ min: 2, max: 100 })
.withMessage('서비스명은 2-100자 사이여야 합니다.'),
body('description')
.trim()
.isLength({ min: 10, max: 5000 })
.withMessage('설명은 10-5000자 사이여야 합니다.'),
body('shortDescription')
.optional()
.trim()
.isLength({ max: 200 })
.withMessage('간단한 설명은 200자 이하여야 합니다.'),
body('category')
.isIn(['development', 'design', 'marketing', 'consulting'])
.withMessage('유효한 카테고리를 선택해주세요.'),
body('pricing.basePrice')
.optional()
.isNumeric()
.withMessage('기본 가격은 숫자여야 합니다.'),
body('pricing.priceType')
.optional()
.isIn(['project', 'hourly', 'monthly'])
.withMessage('유효한 가격 유형을 선택해주세요.'),
handleValidationErrors
];
/**
* Calculator validation
*/
const validateCalculator = [
body('service')
.isMongoId()
.withMessage('유효한 서비스를 선택해주세요.'),
body('projectType')
.optional()
.isIn(['simple', 'medium', 'complex', 'enterprise'])
.withMessage('유효한 프로젝트 유형을 선택해주세요.'),
body('timeline')
.optional()
.isIn(['urgent', 'normal', 'flexible'])
.withMessage('유효한 타임라인을 선택해주세요.'),
body('features')
.optional()
.isArray()
.withMessage('기능은 배열이어야 합니다.'),
body('contactInfo.name')
.optional()
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('이름은 2-50자 사이여야 합니다.'),
body('contactInfo.email')
.optional()
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.'),
body('contactInfo.phone')
.optional()
.matches(/^[0-9\-\+\(\)\s]+$/)
.withMessage('유효한 전화번호를 입력해주세요.'),
handleValidationErrors
];
/**
* Settings validation
*/
const validateSettings = [
body('siteName')
.optional()
.trim()
.isLength({ min: 1, max: 100 })
.withMessage('사이트명은 1-100자 사이여야 합니다.'),
body('siteDescription')
.optional()
.trim()
.isLength({ max: 500 })
.withMessage('사이트 설명은 500자 이하여야 합니다.'),
body('contact.email')
.optional()
.isEmail()
.withMessage('유효한 이메일 주소를 입력해주세요.'),
body('contact.phone')
.optional()
.matches(/^[0-9\-\+\(\)\s]+$/)
.withMessage('유효한 전화번호를 입력해주세요.'),
body('social.facebook')
.optional()
.isURL()
.withMessage('유효한 Facebook URL을 입력해주세요.'),
body('social.twitter')
.optional()
.isURL()
.withMessage('유효한 Twitter URL을 입력해주세요.'),
body('social.linkedin')
.optional()
.isURL()
.withMessage('유효한 LinkedIn URL을 입력해주세요.'),
body('social.instagram')
.optional()
.isURL()
.withMessage('유효한 Instagram URL을 입력해주세요.'),
handleValidationErrors
];
module.exports = {
handleValidationErrors,
validateContactForm,
validateRegistration,
validateLogin,
validatePortfolio,
validateService,
validateCalculator,
validateSettings
};

View File

@@ -0,0 +1,80 @@
const mongoose = require('mongoose');
const contactSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
lowercase: true,
trim: true
},
phone: {
type: String,
trim: true
},
company: {
type: String,
trim: true
},
subject: {
type: String,
required: true,
trim: true
},
message: {
type: String,
required: true
},
serviceInterest: {
type: String,
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other']
},
budget: {
type: String,
enum: ['under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m']
},
timeline: {
type: String,
enum: ['asap', '1-month', '1-3-months', '3-6-months', 'flexible']
},
status: {
type: String,
enum: ['new', 'in-progress', 'replied', 'closed'],
default: 'new'
},
priority: {
type: String,
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium'
},
source: {
type: String,
enum: ['website', 'telegram', 'email', 'phone', 'referral'],
default: 'website'
},
isRead: {
type: Boolean,
default: false
},
adminNotes: {
type: String
},
ipAddress: {
type: String
},
userAgent: {
type: String
}
}, {
timestamps: true
});
contactSchema.index({ status: 1, createdAt: -1 });
contactSchema.index({ isRead: 1, createdAt: -1 });
contactSchema.index({ email: 1 });
module.exports = mongoose.model('Contact', contactSchema);

View File

@@ -0,0 +1,80 @@
const mongoose = require('mongoose');
const contactSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
lowercase: true,
trim: true
},
phone: {
type: String,
trim: true
},
company: {
type: String,
trim: true
},
subject: {
type: String,
required: true,
trim: true
},
message: {
type: String,
required: true
},
serviceInterest: {
type: String,
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other']
},
budget: {
type: String,
enum: ['under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m']
},
timeline: {
type: String,
enum: ['asap', '1-month', '1-3-months', '3-6-months', 'flexible']
},
status: {
type: String,
enum: ['new', 'in-progress', 'replied', 'closed'],
default: 'new'
},
priority: {
type: String,
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium'
},
source: {
type: String,
enum: ['website', 'telegram', 'email', 'phone', 'referral'],
default: 'website'
},
isRead: {
type: Boolean,
default: false
},
adminNotes: {
type: String
},
ipAddress: {
type: String
},
userAgent: {
type: String
}
}, {
timestamps: true
});
contactSchema.index({ status: 1, createdAt: -1 });
contactSchema.index({ isRead: 1, createdAt: -1 });
contactSchema.index({ email: 1 });
module.exports = mongoose.model('Contact', contactSchema);

View File

@@ -0,0 +1,107 @@
const mongoose = require('mongoose');
const portfolioSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true
},
shortDescription: {
type: String,
required: true,
maxlength: 200
},
category: {
type: String,
required: true,
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'e-commerce', 'other']
},
technologies: [{
type: String,
trim: true
}],
images: [{
url: {
type: String,
required: true
},
alt: {
type: String,
default: ''
},
isPrimary: {
type: Boolean,
default: false
}
}],
clientName: {
type: String,
trim: true
},
projectUrl: {
type: String,
trim: true
},
githubUrl: {
type: String,
trim: true
},
status: {
type: String,
enum: ['completed', 'in-progress', 'planning'],
default: 'completed'
},
featured: {
type: Boolean,
default: false
},
publishedAt: {
type: Date,
default: Date.now
},
completedAt: {
type: Date
},
isPublished: {
type: Boolean,
default: true
},
viewCount: {
type: Number,
default: 0
},
likes: {
type: Number,
default: 0
},
order: {
type: Number,
default: 0
},
seo: {
metaTitle: String,
metaDescription: String,
keywords: [String]
}
}, {
timestamps: true
});
// Index for search and sorting
portfolioSchema.index({ title: 'text', description: 'text', technologies: 'text' });
portfolioSchema.index({ category: 1, publishedAt: -1 });
portfolioSchema.index({ featured: -1, publishedAt: -1 });
// Virtual for primary image
portfolioSchema.virtual('primaryImage').get(function() {
const primary = this.images.find(img => img.isPrimary);
return primary || (this.images.length > 0 ? this.images[0] : null);
});
portfolioSchema.set('toJSON', { virtuals: true });
module.exports = mongoose.model('Portfolio', portfolioSchema);

View File

@@ -0,0 +1,107 @@
const mongoose = require('mongoose');
const portfolioSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true
},
shortDescription: {
type: String,
required: true,
maxlength: 200
},
category: {
type: String,
required: true,
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'e-commerce', 'other']
},
technologies: [{
type: String,
trim: true
}],
images: [{
url: {
type: String,
required: true
},
alt: {
type: String,
default: ''
},
isPrimary: {
type: Boolean,
default: false
}
}],
clientName: {
type: String,
trim: true
},
projectUrl: {
type: String,
trim: true
},
githubUrl: {
type: String,
trim: true
},
status: {
type: String,
enum: ['completed', 'in-progress', 'planning'],
default: 'completed'
},
featured: {
type: Boolean,
default: false
},
publishedAt: {
type: Date,
default: Date.now
},
completedAt: {
type: Date
},
isPublished: {
type: Boolean,
default: true
},
viewCount: {
type: Number,
default: 0
},
likes: {
type: Number,
default: 0
},
order: {
type: Number,
default: 0
},
seo: {
metaTitle: String,
metaDescription: String,
keywords: [String]
}
}, {
timestamps: true
});
// Index for search and sorting
portfolioSchema.index({ title: 'text', description: 'text', technologies: 'text' });
portfolioSchema.index({ category: 1, publishedAt: -1 });
portfolioSchema.index({ featured: -1, publishedAt: -1 });
// Virtual for primary image
portfolioSchema.virtual('primaryImage').get(function() {
const primary = this.images.find(img => img.isPrimary);
return primary || (this.images.length > 0 ? this.images[0] : null);
});
portfolioSchema.set('toJSON', { virtuals: true });
module.exports = mongoose.model('Portfolio', portfolioSchema);

View File

@@ -0,0 +1,102 @@
const mongoose = require('mongoose');
const serviceSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true
},
shortDescription: {
type: String,
required: true,
maxlength: 150
},
icon: {
type: String,
required: true
},
category: {
type: String,
required: true,
enum: ['development', 'design', 'consulting', 'marketing', 'maintenance']
},
features: [{
name: String,
description: String,
included: {
type: Boolean,
default: true
}
}],
pricing: {
basePrice: {
type: Number,
required: true,
min: 0
},
currency: {
type: String,
default: 'KRW'
},
priceType: {
type: String,
enum: ['fixed', 'hourly', 'project'],
default: 'project'
},
priceRange: {
min: Number,
max: Number
}
},
estimatedTime: {
min: {
type: Number,
required: true
},
max: {
type: Number,
required: true
},
unit: {
type: String,
enum: ['hours', 'days', 'weeks', 'months'],
default: 'days'
}
},
isActive: {
type: Boolean,
default: true
},
featured: {
type: Boolean,
default: false
},
order: {
type: Number,
default: 0
},
portfolio: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Portfolio'
}],
tags: [{
type: String,
trim: true
}],
seo: {
metaTitle: String,
metaDescription: String,
keywords: [String]
}
}, {
timestamps: true
});
serviceSchema.index({ name: 'text', description: 'text', tags: 'text' });
serviceSchema.index({ category: 1, featured: -1, order: 1 });
module.exports = mongoose.model('Service', serviceSchema);

View File

@@ -0,0 +1,102 @@
const mongoose = require('mongoose');
const serviceSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true
},
shortDescription: {
type: String,
required: true,
maxlength: 150
},
icon: {
type: String,
required: true
},
category: {
type: String,
required: true,
enum: ['development', 'design', 'consulting', 'marketing', 'maintenance']
},
features: [{
name: String,
description: String,
included: {
type: Boolean,
default: true
}
}],
pricing: {
basePrice: {
type: Number,
required: true,
min: 0
},
currency: {
type: String,
default: 'KRW'
},
priceType: {
type: String,
enum: ['fixed', 'hourly', 'project'],
default: 'project'
},
priceRange: {
min: Number,
max: Number
}
},
estimatedTime: {
min: {
type: Number,
required: true
},
max: {
type: Number,
required: true
},
unit: {
type: String,
enum: ['hours', 'days', 'weeks', 'months'],
default: 'days'
}
},
isActive: {
type: Boolean,
default: true
},
featured: {
type: Boolean,
default: false
},
order: {
type: Number,
default: 0
},
portfolio: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Portfolio'
}],
tags: [{
type: String,
trim: true
}],
seo: {
metaTitle: String,
metaDescription: String,
keywords: [String]
}
}, {
timestamps: true
});
serviceSchema.index({ name: 'text', description: 'text', tags: 'text' });
serviceSchema.index({ category: 1, featured: -1, order: 1 });
module.exports = mongoose.model('Service', serviceSchema);

View File

@@ -0,0 +1,116 @@
const mongoose = require('mongoose');
const siteSettingsSchema = new mongoose.Schema({
siteName: {
type: String,
default: 'SmartSolTech'
},
siteDescription: {
type: String,
default: 'Innovative technology solutions for modern businesses'
},
logo: {
type: String,
default: '/images/logo.png'
},
favicon: {
type: String,
default: '/images/favicon.ico'
},
contact: {
email: {
type: String,
default: 'info@smartsoltech.kr'
},
phone: {
type: String,
default: '+82-10-0000-0000'
},
address: {
type: String,
default: 'Seoul, South Korea'
}
},
social: {
facebook: String,
twitter: String,
linkedin: String,
instagram: String,
github: String,
telegram: String
},
telegram: {
botToken: String,
chatId: String,
isEnabled: {
type: Boolean,
default: false
}
},
seo: {
metaTitle: {
type: String,
default: 'SmartSolTech - Technology Solutions'
},
metaDescription: {
type: String,
default: 'Professional web development, mobile apps, and digital solutions in Korea'
},
keywords: {
type: String,
default: 'web development, mobile apps, UI/UX design, Korea, technology'
},
googleAnalytics: String,
googleTagManager: String
},
hero: {
title: {
type: String,
default: 'Smart Technology Solutions'
},
subtitle: {
type: String,
default: 'We create innovative digital experiences that drive business growth'
},
backgroundImage: {
type: String,
default: '/images/hero-bg.jpg'
},
ctaText: {
type: String,
default: 'Get Started'
},
ctaLink: {
type: String,
default: '#contact'
}
},
about: {
title: {
type: String,
default: 'About SmartSolTech'
},
description: {
type: String,
default: 'We are a team of passionate developers and designers creating cutting-edge technology solutions.'
},
image: {
type: String,
default: '/images/about.jpg'
}
},
maintenance: {
isEnabled: {
type: Boolean,
default: false
},
message: {
type: String,
default: 'We are currently performing maintenance. Please check back soon.'
}
}
}, {
timestamps: true
});
module.exports = mongoose.model('SiteSettings', siteSettingsSchema);

View File

@@ -0,0 +1,116 @@
const mongoose = require('mongoose');
const siteSettingsSchema = new mongoose.Schema({
siteName: {
type: String,
default: 'SmartSolTech'
},
siteDescription: {
type: String,
default: 'Innovative technology solutions for modern businesses'
},
logo: {
type: String,
default: '/images/logo.png'
},
favicon: {
type: String,
default: '/images/favicon.ico'
},
contact: {
email: {
type: String,
default: 'info@smartsoltech.kr'
},
phone: {
type: String,
default: '+82-10-0000-0000'
},
address: {
type: String,
default: 'Seoul, South Korea'
}
},
social: {
facebook: String,
twitter: String,
linkedin: String,
instagram: String,
github: String,
telegram: String
},
telegram: {
botToken: String,
chatId: String,
isEnabled: {
type: Boolean,
default: false
}
},
seo: {
metaTitle: {
type: String,
default: 'SmartSolTech - Technology Solutions'
},
metaDescription: {
type: String,
default: 'Professional web development, mobile apps, and digital solutions in Korea'
},
keywords: {
type: String,
default: 'web development, mobile apps, UI/UX design, Korea, technology'
},
googleAnalytics: String,
googleTagManager: String
},
hero: {
title: {
type: String,
default: 'Smart Technology Solutions'
},
subtitle: {
type: String,
default: 'We create innovative digital experiences that drive business growth'
},
backgroundImage: {
type: String,
default: '/images/hero-bg.jpg'
},
ctaText: {
type: String,
default: 'Get Started'
},
ctaLink: {
type: String,
default: '#contact'
}
},
about: {
title: {
type: String,
default: 'About SmartSolTech'
},
description: {
type: String,
default: 'We are a team of passionate developers and designers creating cutting-edge technology solutions.'
},
image: {
type: String,
default: '/images/about.jpg'
}
},
maintenance: {
isEnabled: {
type: Boolean,
default: false
},
message: {
type: String,
default: 'We are currently performing maintenance. Please check back soon.'
}
}
}, {
timestamps: true
});
module.exports = mongoose.model('SiteSettings', siteSettingsSchema);

View File

@@ -0,0 +1,75 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
password: {
type: String,
required: true,
minlength: 6
},
name: {
type: String,
required: true,
trim: true
},
role: {
type: String,
enum: ['admin', 'moderator'],
default: 'admin'
},
avatar: {
type: String,
default: null
},
isActive: {
type: Boolean,
default: true
},
lastLogin: {
type: Date,
default: null
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(12);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// Compare password method
userSchema.methods.comparePassword = async function(candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
// Update last login
userSchema.methods.updateLastLogin = function() {
this.lastLogin = new Date();
return this.save();
};
module.exports = mongoose.model('User', userSchema);

View File

@@ -0,0 +1,75 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
password: {
type: String,
required: true,
minlength: 6
},
name: {
type: String,
required: true,
trim: true
},
role: {
type: String,
enum: ['admin', 'moderator'],
default: 'admin'
},
avatar: {
type: String,
default: null
},
isActive: {
type: Boolean,
default: true
},
lastLogin: {
type: Date,
default: null
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(12);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// Compare password method
userSchema.methods.comparePassword = async function(candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
// Update last login
userSchema.methods.updateLastLogin = function() {
this.lastLogin = new Date();
return this.save();
};
module.exports = mongoose.model('User', userSchema);

View File

@@ -0,0 +1,48 @@
{
"name": "smartsoltech-website",
"version": "1.0.0",
"description": "Modern PWA website for SmartSolTech with admin panel and Telegram integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"watch": "webpack --mode development --watch"
},
"keywords": ["pwa", "nodejs", "express", "telegram", "portfolio", "calculator"],
"author": "SmartSolTech",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.3",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0",
"node-telegram-bot-api": "^0.64.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"nodemailer": "^6.9.7",
"express-validator": "^7.0.1",
"socket.io": "^4.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.9",
"workbox-webpack-plugin": "^7.0.0"
}
}

View File

@@ -0,0 +1,48 @@
{
"name": "smartsoltech-website",
"version": "1.0.0",
"description": "Modern PWA website for SmartSolTech with admin panel and Telegram integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"watch": "webpack --mode development --watch"
},
"keywords": ["pwa", "nodejs", "express", "telegram", "portfolio", "calculator"],
"author": "SmartSolTech",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.3",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0",
"node-telegram-bot-api": "^0.64.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"nodemailer": "^6.9.7",
"express-validator": "^7.0.1",
"socket.io": "^4.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.9",
"workbox-webpack-plugin": "^7.0.0"
}
}

View File

@@ -0,0 +1,48 @@
{
"name": "smartsoltech-website",
"version": "1.0.0",
"description": "Modern PWA website for SmartSolTech with admin panel and Telegram integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"init-db": "node scripts/init-db.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["pwa", "nodejs", "express", "telegram", "portfolio", "calculator"],
"author": "SmartSolTech",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.3",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0",
"node-telegram-bot-api": "^0.64.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"nodemailer": "^6.9.7",
"express-validator": "^7.0.1",
"socket.io": "^4.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.9",
"workbox-webpack-plugin": "^7.0.0"
}
}

View File

@@ -0,0 +1,48 @@
{
"name": "smartsoltech-website",
"version": "1.0.0",
"description": "Modern PWA website for SmartSolTech with admin panel and Telegram integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"init-db": "node scripts/init-db.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["pwa", "nodejs", "express", "telegram", "portfolio", "calculator"],
"author": "SmartSolTech",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.3",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0",
"node-telegram-bot-api": "^0.64.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"nodemailer": "^6.9.7",
"express-validator": "^7.0.1",
"socket.io": "^4.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.9",
"workbox-webpack-plugin": "^7.0.0"
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "smartsoltech-website",
"version": "1.0.0",
"description": "Modern PWA website for SmartSolTech with admin panel and Telegram integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"demo": "node server-demo.js",
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"init-db": "node scripts/init-db.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["pwa", "nodejs", "express", "telegram", "portfolio", "calculator"],
"author": "SmartSolTech",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.3",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0",
"node-telegram-bot-api": "^0.64.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"nodemailer": "^6.9.7",
"express-validator": "^7.0.1",
"socket.io": "^4.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.9",
"workbox-webpack-plugin": "^7.0.0"
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "smartsoltech-website",
"version": "1.0.0",
"description": "Modern PWA website for SmartSolTech with admin panel and Telegram integration",
"main": "server.js",
"scripts": {
"start": "node server.js",
"demo": "node server-demo.js",
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"init-db": "node scripts/init-db.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["pwa", "nodejs", "express", "telegram", "portfolio", "calculator"],
"author": "SmartSolTech",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.3",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0",
"node-telegram-bot-api": "^0.64.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"nodemailer": "^6.9.7",
"express-validator": "^7.0.1",
"socket.io": "^4.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.9",
"workbox-webpack-plugin": "^7.0.0"
}
}

View File

@@ -0,0 +1,88 @@
/* SmartSolTech - Main Styles */
/* Basic reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Additional styles for demo */
.hero-section {
padding: 6rem 0 4rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
}
.section-padding {
padding: 4rem 0;
}
.card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.btn-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
transition: all 0.3s ease;
}
.btn-gradient:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
/* Loading spinner */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
/* Responsive utilities */
@media (max-width: 768px) {
.hero-section {
padding: 4rem 0 3rem;
}
.section-padding {
padding: 2rem 0;
}
}

View File

@@ -0,0 +1,88 @@
/* SmartSolTech - Main Styles */
/* Basic reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Additional styles for demo */
.hero-section {
padding: 6rem 0 4rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
}
.section-padding {
padding: 4rem 0;
}
.card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.btn-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
transition: all 0.3s ease;
}
.btn-gradient:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
/* Loading spinner */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
/* Responsive utilities */
@media (max-width: 768px) {
.hero-section {
padding: 4rem 0 3rem;
}
.section-padding {
padding: 2rem 0;
}
}

View File

@@ -0,0 +1,315 @@
/* Dark Theme Support for SmartSolTech */
/* Base Dark Theme */
html.dark {
color-scheme: dark;
}
.dark body {
background-color: #111827;
color: #f9fafb;
}
/* Navigation Dark Theme */
.dark nav {
background-color: #1f2937;
border-color: #374151;
}
.dark .nav-link {
color: #d1d5db;
}
.dark .nav-link:hover {
color: #60a5fa;
}
.dark .nav-link.active {
color: #60a5fa;
}
/* Sections Dark Theme */
.dark section {
background-color: #111827;
color: #f9fafb;
}
.dark .bg-white {
background-color: #1f2937 !important;
}
.dark .bg-gray-50 {
background-color: #111827 !important;
}
.dark .bg-gray-100 {
background-color: #374151 !important;
}
.dark .bg-gray-900 {
background-color: #030712 !important;
}
/* Text Colors Dark Theme */
.dark .text-gray-900 {
color: #f9fafb !important;
}
.dark .text-gray-800 {
color: #e5e7eb !important;
}
.dark .text-gray-700 {
color: #d1d5db !important;
}
.dark .text-gray-600 {
color: #9ca3af !important;
}
.dark .text-gray-500 {
color: #6b7280 !important;
}
.dark .text-gray-400 {
color: #9ca3af !important;
}
.dark .text-gray-300 {
color: #d1d5db !important;
}
/* Cards Dark Theme */
.dark .card-hover {
background-color: #1f2937;
border-color: #374151;
}
.dark .card-hover:hover {
background-color: #374151;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
}
/* Forms Dark Theme */
.dark .form-input {
background-color: #374151;
border-color: #4b5563;
color: #f9fafb;
}
.dark .form-input:focus {
border-color: #60a5fa;
background-color: #1f2937;
}
.dark .form-input::placeholder {
color: #9ca3af;
}
.dark input,
.dark textarea,
.dark select {
background-color: #374151;
border-color: #4b5563;
color: #f9fafb;
}
.dark input:focus,
.dark textarea:focus,
.dark select:focus {
border-color: #60a5fa;
background-color: #1f2937;
}
/* Borders Dark Theme */
.dark .border-gray-300 {
border-color: #4b5563 !important;
}
.dark .border-gray-200 {
border-color: #374151 !important;
}
.dark .border-t {
border-color: #374151;
}
/* Contact Form Dark Theme */
.dark .contact-form {
background: linear-gradient(145deg, rgba(31,41,55,0.9), rgba(31,41,55,0.95));
border-color: #374151;
}
/* Service Cards Dark Theme */
.dark .service-card {
background: linear-gradient(145deg, rgba(31,41,55,0.8), rgba(31,41,55,0.6));
border-color: #374151;
}
/* Team Cards Dark Theme */
.dark .team-card {
background-color: #1f2937;
border-color: #374151;
}
/* Portfolio Items Dark Theme */
.dark .portfolio-item {
background-color: #1f2937;
}
/* Hero Section Dark Theme */
.dark .hero-section {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
}
/* Shadows Dark Theme */
.dark .shadow-lg {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
.dark .shadow-xl {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
}
.dark .shadow-2xl {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
/* Dropdown Dark Theme */
.dark .dropdown-menu {
background-color: #1f2937;
border-color: #374151;
}
.dark .dropdown-menu a {
color: #d1d5db;
}
.dark .dropdown-menu a:hover {
background-color: #374151;
color: #f9fafb;
}
/* Icons Dark Theme */
.dark .text-blue-600 {
color: #60a5fa !important;
}
.dark .text-purple-600 {
color: #a78bfa !important;
}
.dark .text-green-600 {
color: #34d399 !important;
}
.dark .text-yellow-600 {
color: #fbbf24 !important;
}
/* Buttons Dark Theme */
.dark .btn-primary {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
.dark .btn-primary:hover {
background: linear-gradient(135deg, #1d4ed8, #1e40af);
}
/* Footer Dark Theme */
.dark footer {
background-color: #030712;
border-color: #1f2937;
}
.dark .footer-gradient {
background: linear-gradient(135deg, #030712 0%, #111827 100%);
}
/* Scrollbar Dark Theme */
.dark ::-webkit-scrollbar-track {
background: #1f2937;
}
.dark ::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
/* Technology Stack Dark Theme */
.dark .tech-stack {
background-color: #030712;
}
.dark .tech-card {
background-color: #1f2937;
border-color: #374151;
}
/* CTA Section Dark Theme */
.dark .cta-section {
background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
}
/* Testimonials Dark Theme */
.dark .testimonial-card {
background: rgba(31, 41, 55, 0.8);
border-color: #374151;
}
/* Alert Messages Dark Theme */
.dark .alert-success {
background: rgba(16, 185, 129, 0.1);
border-color: rgba(16, 185, 129, 0.3);
color: #10b981;
}
.dark .alert-error {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: #ef4444;
}
/* Mobile Menu Dark Theme */
.dark #mobile-menu {
background-color: #1f2937;
border-color: #374151;
}
/* Language Dropdown Dark Theme */
.dark #mobile-language-menu {
background-color: #1f2937;
border-color: #374151;
}
/* Smooth Theme Transition */
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
/* Print styles for dark theme */
@media print {
.dark * {
background: white !important;
color: black !important;
}
}
/* High contrast mode */
@media (prefers-contrast: high) {
.dark {
--tw-bg-opacity: 1;
--tw-text-opacity: 1;
}
.dark .border {
border-width: 2px;
}
}
/* Reduced motion for accessibility */
@media (prefers-reduced-motion: reduce) {
.dark * {
transition: none !important;
animation: none !important;
}
}

View File

@@ -0,0 +1,315 @@
/* Dark Theme Support for SmartSolTech */
/* Base Dark Theme */
html.dark {
color-scheme: dark;
}
.dark body {
background-color: #111827;
color: #f9fafb;
}
/* Navigation Dark Theme */
.dark nav {
background-color: #1f2937;
border-color: #374151;
}
.dark .nav-link {
color: #d1d5db;
}
.dark .nav-link:hover {
color: #60a5fa;
}
.dark .nav-link.active {
color: #60a5fa;
}
/* Sections Dark Theme */
.dark section {
background-color: #111827;
color: #f9fafb;
}
.dark .bg-white {
background-color: #1f2937 !important;
}
.dark .bg-gray-50 {
background-color: #111827 !important;
}
.dark .bg-gray-100 {
background-color: #374151 !important;
}
.dark .bg-gray-900 {
background-color: #030712 !important;
}
/* Text Colors Dark Theme */
.dark .text-gray-900 {
color: #f9fafb !important;
}
.dark .text-gray-800 {
color: #e5e7eb !important;
}
.dark .text-gray-700 {
color: #d1d5db !important;
}
.dark .text-gray-600 {
color: #9ca3af !important;
}
.dark .text-gray-500 {
color: #6b7280 !important;
}
.dark .text-gray-400 {
color: #9ca3af !important;
}
.dark .text-gray-300 {
color: #d1d5db !important;
}
/* Cards Dark Theme */
.dark .card-hover {
background-color: #1f2937;
border-color: #374151;
}
.dark .card-hover:hover {
background-color: #374151;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
}
/* Forms Dark Theme */
.dark .form-input {
background-color: #374151;
border-color: #4b5563;
color: #f9fafb;
}
.dark .form-input:focus {
border-color: #60a5fa;
background-color: #1f2937;
}
.dark .form-input::placeholder {
color: #9ca3af;
}
.dark input,
.dark textarea,
.dark select {
background-color: #374151;
border-color: #4b5563;
color: #f9fafb;
}
.dark input:focus,
.dark textarea:focus,
.dark select:focus {
border-color: #60a5fa;
background-color: #1f2937;
}
/* Borders Dark Theme */
.dark .border-gray-300 {
border-color: #4b5563 !important;
}
.dark .border-gray-200 {
border-color: #374151 !important;
}
.dark .border-t {
border-color: #374151;
}
/* Contact Form Dark Theme */
.dark .contact-form {
background: linear-gradient(145deg, rgba(31,41,55,0.9), rgba(31,41,55,0.95));
border-color: #374151;
}
/* Service Cards Dark Theme */
.dark .service-card {
background: linear-gradient(145deg, rgba(31,41,55,0.8), rgba(31,41,55,0.6));
border-color: #374151;
}
/* Team Cards Dark Theme */
.dark .team-card {
background-color: #1f2937;
border-color: #374151;
}
/* Portfolio Items Dark Theme */
.dark .portfolio-item {
background-color: #1f2937;
}
/* Hero Section Dark Theme */
.dark .hero-section {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
}
/* Shadows Dark Theme */
.dark .shadow-lg {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
.dark .shadow-xl {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
}
.dark .shadow-2xl {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
/* Dropdown Dark Theme */
.dark .dropdown-menu {
background-color: #1f2937;
border-color: #374151;
}
.dark .dropdown-menu a {
color: #d1d5db;
}
.dark .dropdown-menu a:hover {
background-color: #374151;
color: #f9fafb;
}
/* Icons Dark Theme */
.dark .text-blue-600 {
color: #60a5fa !important;
}
.dark .text-purple-600 {
color: #a78bfa !important;
}
.dark .text-green-600 {
color: #34d399 !important;
}
.dark .text-yellow-600 {
color: #fbbf24 !important;
}
/* Buttons Dark Theme */
.dark .btn-primary {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
.dark .btn-primary:hover {
background: linear-gradient(135deg, #1d4ed8, #1e40af);
}
/* Footer Dark Theme */
.dark footer {
background-color: #030712;
border-color: #1f2937;
}
.dark .footer-gradient {
background: linear-gradient(135deg, #030712 0%, #111827 100%);
}
/* Scrollbar Dark Theme */
.dark ::-webkit-scrollbar-track {
background: #1f2937;
}
.dark ::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
/* Technology Stack Dark Theme */
.dark .tech-stack {
background-color: #030712;
}
.dark .tech-card {
background-color: #1f2937;
border-color: #374151;
}
/* CTA Section Dark Theme */
.dark .cta-section {
background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
}
/* Testimonials Dark Theme */
.dark .testimonial-card {
background: rgba(31, 41, 55, 0.8);
border-color: #374151;
}
/* Alert Messages Dark Theme */
.dark .alert-success {
background: rgba(16, 185, 129, 0.1);
border-color: rgba(16, 185, 129, 0.3);
color: #10b981;
}
.dark .alert-error {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: #ef4444;
}
/* Mobile Menu Dark Theme */
.dark #mobile-menu {
background-color: #1f2937;
border-color: #374151;
}
/* Language Dropdown Dark Theme */
.dark #mobile-language-menu {
background-color: #1f2937;
border-color: #374151;
}
/* Smooth Theme Transition */
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
/* Print styles for dark theme */
@media print {
.dark * {
background: white !important;
color: black !important;
}
}
/* High contrast mode */
@media (prefers-contrast: high) {
.dark {
--tw-bg-opacity: 1;
--tw-text-opacity: 1;
}
.dark .border {
border-width: 2px;
}
}
/* Reduced motion for accessibility */
@media (prefers-reduced-motion: reduce) {
.dark * {
transition: none !important;
animation: none !important;
}
}

View File

@@ -0,0 +1,281 @@
/* SmartSolTech - Design Fixes & Enhancements */
/* Glass effect improvements */
.glass-effect {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
/* Fallback for browsers that don't support backdrop-filter */
}
/* Support backdrop-filter for modern browsers */
@supports (backdrop-filter: blur(10px)) {
.glass-effect {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
}
/* Hero section improvements */
.hero-section {
position: relative;
overflow: hidden;
min-height: 100vh;
}
/* Background blob animations */
@keyframes blob {
0% { transform: translate(0px, 0px) scale(1); }
33% { transform: translate(30px, -50px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
100% { transform: translate(0px, 0px) scale(1); }
}
.animate-blob {
animation: blob 7s infinite;
}
.animation-delay-2000 {
animation-delay: 2s;
}
.animation-delay-4000 {
animation-delay: 4s;
}
/* Enhanced card hover effects */
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backface-visibility: hidden;
}
.card-hover:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
}
/* Portfolio item enhancements */
.portfolio-item {
overflow: hidden;
border-radius: 16px;
position: relative;
}
.portfolio-image {
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.portfolio-item:hover .portfolio-image {
transform: scale(1.1);
}
/* Button improvements */
.btn-primary {
background: linear-gradient(135deg, #3B82F6, #1D4ED8);
border: none;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: left 0.5s;
}
.btn-primary:hover::before {
left: 100%;
}
.btn-primary:hover {
background: linear-gradient(135deg, #1D4ED8, #1E40AF);
transform: translateY(-3px);
box-shadow: 0 15px 35px rgba(59, 130, 246, 0.4);
}
/* Navigation improvements */
.nav-link {
position: relative;
transition: all 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
width: 0;
height: 2px;
background: linear-gradient(90deg, #3B82F6, #8B5CF6);
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-link:hover::after,
.nav-link.active::after {
width: 100%;
}
/* Form improvements */
.form-input {
transition: all 0.3s ease;
border: 2px solid #E5E7EB;
background: rgba(255, 255, 255, 0.95);
}
.form-input:focus {
border-color: #3B82F6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
transform: translateY(-1px);
}
/* Contact form styling */
.contact-form {
background: linear-gradient(145deg, rgba(255,255,255,0.9), rgba(255,255,255,0.95));
border: 1px solid rgba(255,255,255,0.3);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
/* CTA section improvements */
.cta-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
}
.cta-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
}
/* Service cards */
.service-card {
background: linear-gradient(145deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
border: 1px solid rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* Team member cards */
.team-card {
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.95);
}
.team-card:hover {
transform: translateY(-10px);
box-shadow: 0 30px 60px rgba(0,0,0,0.12);
}
/* Technology icons */
.tech-icon {
transition: all 0.3s ease;
}
.tech-icon:hover {
transform: scale(1.1) rotate(5deg);
filter: brightness(1.2);
}
/* Loading states */
.loading {
opacity: 0.7;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3B82F6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Mobile optimizations */
@media (max-width: 768px) {
.card-hover:hover {
transform: translateY(-4px) scale(1.01);
}
.btn-primary:hover {
transform: translateY(-2px);
}
.hero-section {
min-height: 80vh;
}
.portfolio-item:hover .portfolio-image {
transform: scale(1.05);
}
}
/* Accessibility improvements */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
button:focus,
input:focus,
textarea:focus,
select:focus,
a:focus {
outline: 2px solid #3B82F6;
outline-offset: 2px;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Print styles */
@media print {
.no-print {
display: none !important;
}
body {
color: black !important;
background: white !important;
}
}
/* Dark mode support (if needed) */
@media (prefers-color-scheme: dark) {
.auto-dark {
color: #f9fafb;
background-color: #111827;
}
}

View File

@@ -0,0 +1,281 @@
/* SmartSolTech - Design Fixes & Enhancements */
/* Glass effect improvements */
.glass-effect {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
/* Fallback for browsers that don't support backdrop-filter */
}
/* Support backdrop-filter for modern browsers */
@supports (backdrop-filter: blur(10px)) {
.glass-effect {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
}
/* Hero section improvements */
.hero-section {
position: relative;
overflow: hidden;
min-height: 100vh;
}
/* Background blob animations */
@keyframes blob {
0% { transform: translate(0px, 0px) scale(1); }
33% { transform: translate(30px, -50px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
100% { transform: translate(0px, 0px) scale(1); }
}
.animate-blob {
animation: blob 7s infinite;
}
.animation-delay-2000 {
animation-delay: 2s;
}
.animation-delay-4000 {
animation-delay: 4s;
}
/* Enhanced card hover effects */
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backface-visibility: hidden;
}
.card-hover:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
}
/* Portfolio item enhancements */
.portfolio-item {
overflow: hidden;
border-radius: 16px;
position: relative;
}
.portfolio-image {
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.portfolio-item:hover .portfolio-image {
transform: scale(1.1);
}
/* Button improvements */
.btn-primary {
background: linear-gradient(135deg, #3B82F6, #1D4ED8);
border: none;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: left 0.5s;
}
.btn-primary:hover::before {
left: 100%;
}
.btn-primary:hover {
background: linear-gradient(135deg, #1D4ED8, #1E40AF);
transform: translateY(-3px);
box-shadow: 0 15px 35px rgba(59, 130, 246, 0.4);
}
/* Navigation improvements */
.nav-link {
position: relative;
transition: all 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
width: 0;
height: 2px;
background: linear-gradient(90deg, #3B82F6, #8B5CF6);
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-link:hover::after,
.nav-link.active::after {
width: 100%;
}
/* Form improvements */
.form-input {
transition: all 0.3s ease;
border: 2px solid #E5E7EB;
background: rgba(255, 255, 255, 0.95);
}
.form-input:focus {
border-color: #3B82F6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
transform: translateY(-1px);
}
/* Contact form styling */
.contact-form {
background: linear-gradient(145deg, rgba(255,255,255,0.9), rgba(255,255,255,0.95));
border: 1px solid rgba(255,255,255,0.3);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
/* CTA section improvements */
.cta-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
}
.cta-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
}
/* Service cards */
.service-card {
background: linear-gradient(145deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
border: 1px solid rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* Team member cards */
.team-card {
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.95);
}
.team-card:hover {
transform: translateY(-10px);
box-shadow: 0 30px 60px rgba(0,0,0,0.12);
}
/* Technology icons */
.tech-icon {
transition: all 0.3s ease;
}
.tech-icon:hover {
transform: scale(1.1) rotate(5deg);
filter: brightness(1.2);
}
/* Loading states */
.loading {
opacity: 0.7;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3B82F6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Mobile optimizations */
@media (max-width: 768px) {
.card-hover:hover {
transform: translateY(-4px) scale(1.01);
}
.btn-primary:hover {
transform: translateY(-2px);
}
.hero-section {
min-height: 80vh;
}
.portfolio-item:hover .portfolio-image {
transform: scale(1.05);
}
}
/* Accessibility improvements */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
button:focus,
input:focus,
textarea:focus,
select:focus,
a:focus {
outline: 2px solid #3B82F6;
outline-offset: 2px;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Print styles */
@media print {
.no-print {
display: none !important;
}
body {
color: black !important;
background: white !important;
}
}
/* Dark mode support (if needed) */
@media (prefers-color-scheme: dark) {
.auto-dark {
color: #f9fafb;
background-color: #111827;
}
}

View File

@@ -0,0 +1,425 @@
/* Custom CSS for SmartSolTech */
:root {
--primary-color: #3b82f6;
--secondary-color: #8b5cf6;
--accent-color: #10b981;
--text-dark: #1f2937;
--text-light: #6b7280;
--bg-light: #f9fafb;
--border-color: #e5e7eb;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: var(--text-dark);
scroll-behavior: smooth;
}
/* Loading Animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Navigation Enhancements */
.navbar-scrolled {
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
/* Mobile Menu Animation */
.mobile-menu {
transition: all 0.3s ease-in-out;
max-height: 0;
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}

View File

@@ -0,0 +1,425 @@
/* Custom CSS for SmartSolTech */
:root {
--primary-color: #3b82f6;
--secondary-color: #8b5cf6;
--accent-color: #10b981;
--text-dark: #1f2937;
--text-light: #6b7280;
--bg-light: #f9fafb;
--border-color: #e5e7eb;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: var(--text-dark);
scroll-behavior: smooth;
}
/* Loading Animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Navigation Enhancements */
.navbar-scrolled {
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
/* Mobile Menu Animation */
.mobile-menu {
transition: all 0.3s ease-in-out;
max-height: 0;
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}

View File

@@ -0,0 +1,427 @@
/* SmartSolTech - Main Styles */
/* CSS Reset and Base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Utility Classes */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.section-padding {
padding: 4rem 0;
}
/* Loading Animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Navigation Enhancements */
.navbar-scrolled {
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
/* Mobile Menu Animation */
.mobile-menu {
transition: all 0.3s ease-in-out;
max-height: 0;
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}

View File

@@ -0,0 +1,442 @@
/* SmartSolTech - Main Styles */
/* CSS Reset and Base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Utility Classes */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.section-padding {
padding: 4rem 0;
}
/* Navigation */
.navbar {
background: rgba(255, 255, 255, 0.95);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
transition: all 0.3s ease;
}
.navbar-brand {
font-size: 1.5rem;
font-weight: bold;
color: #3b82f6;
text-decoration: none;
}
.navbar-nav {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-link {
color: #6b7280;
text-decoration: none;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.nav-link:hover,
.nav-link.active {
color: #3b82f6;
background-color: #eff6ff;
}
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}

View File

@@ -0,0 +1,442 @@
/* SmartSolTech - Main Styles */
/* CSS Reset and Base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Utility Classes */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.section-padding {
padding: 4rem 0;
}
/* Navigation */
.navbar {
background: rgba(255, 255, 255, 0.95);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
transition: all 0.3s ease;
}
.navbar-brand {
font-size: 1.5rem;
font-weight: bold;
color: #3b82f6;
text-decoration: none;
}
.navbar-nav {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-link {
color: #6b7280;
text-decoration: none;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.nav-link:hover,
.nav-link.active {
color: #3b82f6;
background-color: #eff6ff;
}
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}

View File

@@ -0,0 +1,552 @@
/* SmartSolTech - Main Styles */
/* CSS Reset and Base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Utility Classes */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.section-padding {
padding: 4rem 0;
}
/* Navigation */
.navbar {
background: rgba(255, 255, 255, 0.95);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
transition: all 0.3s ease;
}
.navbar-brand {
font-size: 1.5rem;
font-weight: bold;
color: #3b82f6;
text-decoration: none;
}
.navbar-nav {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-link {
color: #6b7280;
text-decoration: none;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.nav-link:hover,
.nav-link.active {
color: #3b82f6;
background-color: #eff6ff;
}
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Calculator Styles */
.calculator-step {
display: none;
}
.calculator-step.active {
display: block;
}
.service-option,
.complexity-option,
.timeline-option {
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.service-option:hover,
.complexity-option:hover,
.timeline-option:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
}
.service-option.selected,
.complexity-option.selected,
.timeline-option.selected {
border-color: #3B82F6 !important;
background-color: #EBF8FF !important;
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
}
.service-option.selected::after,
.complexity-option.selected::after,
.timeline-option.selected::after {
content: '✓';
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 24px;
height: 24px;
background: #3B82F6;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
}
/* Price Display Animation */
#final-price {
animation: priceReveal 0.8s ease-in-out;
}
@keyframes priceReveal {
0% {
opacity: 0;
transform: scale(0.8);
}
50% {
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}
/* Calculator Progress Bar */
.calculator-progress {
width: 100%;
height: 4px;
background: #e5e7eb;
border-radius: 2px;
margin-bottom: 2rem;
overflow: hidden;
}
.calculator-progress-bar {
height: 100%;
background: linear-gradient(90deg, #3B82F6, #8B5CF6);
border-radius: 2px;
transition: width 0.3s ease;
width: 33.33%;
}
.calculator-progress-bar.step-2 {
width: 66.66%;
}
.calculator-progress-bar.step-3 {
width: 100%;
}
/* Calculator Mobile Improvements */
@media (max-width: 768px) {
.service-option,
.complexity-option,
.timeline-option {
margin-bottom: 1rem;
}
#final-price {
font-size: 2.5rem;
}
}

View File

@@ -0,0 +1,552 @@
/* SmartSolTech - Main Styles */
/* CSS Reset and Base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #1f2937;
}
/* Utility Classes */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.section-padding {
padding: 4rem 0;
}
/* Navigation */
.navbar {
background: rgba(255, 255, 255, 0.95);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
transition: all 0.3s ease;
}
.navbar-brand {
font-size: 1.5rem;
font-weight: bold;
color: #3b82f6;
text-decoration: none;
}
.navbar-nav {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-link {
color: #6b7280;
text-decoration: none;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.nav-link:hover,
.nav-link.active {
color: #3b82f6;
background-color: #eff6ff;
}
overflow: hidden;
}
.mobile-menu.show {
max-height: 500px;
}
/* Button Hover Effects */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
transition: all 0.3s ease;
transform: translateY(0);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
/* Portfolio Grid */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.portfolio-item {
border-radius: 1rem;
overflow: hidden;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.portfolio-image {
position: relative;
overflow: hidden;
aspect-ratio: 16/10;
}
.portfolio-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.portfolio-item:hover .portfolio-image img {
transform: scale(1.05);
}
.portfolio-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.8));
opacity: 0;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.portfolio-item:hover .portfolio-overlay {
opacity: 1;
}
/* Service Cards */
.service-card {
background: white;
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.service-card:hover {
border-color: var(--primary-color);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(59, 130, 246, 0.1);
}
.service-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
font-size: 2rem;
color: white;
transition: all 0.3s ease;
}
.service-card:hover .service-icon {
transform: scale(1.1) rotate(5deg);
}
/* Contact Form */
.contact-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 1rem;
border: 2px solid var(--border-color);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Calculator Styles */
.calculator-step {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.option-card {
border: 2px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: var(--primary-color);
background: rgba(59, 130, 246, 0.05);
}
/* Progress Bar */
.progress-bar {
width: 100%;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
margin-bottom: 2rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
transition: width 0.3s ease;
}
/* Hero Section Animations */
.hero-content {
animation: heroFadeIn 1s ease-out;
}
@keyframes heroFadeIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Parallax Effect */
.parallax {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
/* Scroll Animations */
.fade-in-up {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease;
}
.fade-in-up.animate {
opacity: 1;
transform: translateY(0);
}
/* Typography */
.gradient-text {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-new {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.status-in-progress {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.status-completed {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.portfolio-grid {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 2.5rem;
}
.service-card {
padding: 1.5rem;
}
.calculator-step {
padding: 1rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--text-dark: #f9fafb;
--text-light: #d1d5db;
--bg-light: #1f2937;
--border-color: #374151;
}
body {
background-color: #111827;
color: var(--text-dark);
}
.card-hover, .service-card, .contact-form, .option-card {
background: #1f2937;
border-color: var(--border-color);
}
.form-control {
background: #374151;
border-color: #4b5563;
color: white;
}
.form-control:focus {
border-color: var(--primary-color);
}
}
/* Print Styles */
@media print {
.navbar, .footer, .contact-form, .mobile-menu {
display: none !important;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.portfolio-item, .service-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for better accessibility */
.form-control:focus,
.btn:focus,
.option-card:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading States */
.btn-loading {
position: relative;
color: transparent;
}
.btn-loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Calculator Styles */
.calculator-step {
display: none;
}
.calculator-step.active {
display: block;
}
.service-option,
.complexity-option,
.timeline-option {
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.service-option:hover,
.complexity-option:hover,
.timeline-option:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
}
.service-option.selected,
.complexity-option.selected,
.timeline-option.selected {
border-color: #3B82F6 !important;
background-color: #EBF8FF !important;
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
}
.service-option.selected::after,
.complexity-option.selected::after,
.timeline-option.selected::after {
content: '✓';
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 24px;
height: 24px;
background: #3B82F6;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
}
/* Price Display Animation */
#final-price {
animation: priceReveal 0.8s ease-in-out;
}
@keyframes priceReveal {
0% {
opacity: 0;
transform: scale(0.8);
}
50% {
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}
/* Calculator Progress Bar */
.calculator-progress {
width: 100%;
height: 4px;
background: #e5e7eb;
border-radius: 2px;
margin-bottom: 2rem;
overflow: hidden;
}
.calculator-progress-bar {
height: 100%;
background: linear-gradient(90deg, #3B82F6, #8B5CF6);
border-radius: 2px;
transition: width 0.3s ease;
width: 33.33%;
}
.calculator-progress-bar.step-2 {
width: 66.66%;
}
.calculator-progress-bar.step-3 {
width: 100%;
}
/* Calculator Mobile Improvements */
@media (max-width: 768px) {
.service-option,
.complexity-option,
.timeline-option {
margin-bottom: 1rem;
}
#final-price {
font-size: 2.5rem;
}
}

View File

@@ -0,0 +1,368 @@
// Calculator Logic
class ProjectCalculator {
constructor() {
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
this.totalSteps = 3;
this.init();
}
init() {
this.setupEventListeners();
this.updateProgressBar();
}
setupEventListeners() {
// Service selection
document.querySelectorAll('.service-option').forEach(option => {
option.addEventListener('click', (e) => this.handleServiceSelection(e));
});
// Complexity selection
document.querySelectorAll('.complexity-option').forEach(option => {
option.addEventListener('click', (e) => this.handleComplexitySelection(e));
});
// Timeline selection
document.querySelectorAll('.timeline-option').forEach(option => {
option.addEventListener('click', (e) => this.handleTimelineSelection(e));
});
// Navigation buttons
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', () => this.nextStep());
});
document.querySelectorAll('.prev-step').forEach(btn => {
btn.addEventListener('click', () => this.prevStep());
});
// Restart button
const restartBtn = document.querySelector('.restart-calculator');
if (restartBtn) {
restartBtn.addEventListener('click', () => this.restart());
}
}
handleServiceSelection(e) {
const option = e.currentTarget;
const service = option.dataset.service;
const price = parseInt(option.dataset.basePrice);
option.classList.toggle('selected');
if (option.classList.contains('selected')) {
this.selectedServices.push({ service, price });
this.animateSelection(option, true);
} else {
this.selectedServices = this.selectedServices.filter(s => s.service !== service);
this.animateSelection(option, false);
}
this.updateStepButton();
}
handleComplexitySelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.complexity-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedComplexity = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
handleTimelineSelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedTimeline = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
animateSelection(element, selected) {
if (selected) {
element.style.borderColor = '#3B82F6';
element.style.backgroundColor = '#EBF8FF';
element.style.transform = 'translateY(-2px)';
element.style.boxShadow = '0 8px 25px rgba(59, 130, 246, 0.15)';
} else {
element.style.borderColor = '';
element.style.backgroundColor = '';
element.style.transform = '';
element.style.boxShadow = '';
}
}
updateStepButton() {
const step1Button = document.querySelector('#step-1 .next-step');
const step2Button = document.querySelector('#step-2 .next-step');
if (step1Button) {
step1Button.disabled = this.selectedServices.length === 0;
step1Button.style.opacity = this.selectedServices.length > 0 ? '1' : '0.6';
}
if (step2Button) {
const isValid = this.selectedComplexity && this.selectedTimeline;
step2Button.disabled = !isValid;
step2Button.style.opacity = isValid ? '1' : '0.6';
}
}
updateProgressBar() {
const progressBar = document.querySelector('.calculator-progress-bar');
if (progressBar) {
const progress = (this.currentStep / this.totalSteps) * 100;
progressBar.style.width = `${progress}%`;
progressBar.className = `calculator-progress-bar step-${this.currentStep}`;
}
}
nextStep() {
if (this.currentStep >= this.totalSteps) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const nextStepElement = currentStepElement.nextElementSibling;
if (nextStepElement && nextStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in next step
nextStepElement.classList.add('active');
nextStepElement.style.display = 'block';
nextStepElement.style.opacity = '0';
nextStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
nextStepElement.style.opacity = '1';
nextStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep++;
this.updateProgressBar();
if (this.currentStep === 3) {
setTimeout(() => this.calculateFinalPrice(), 300);
}
}, 200);
}
}
prevStep() {
if (this.currentStep <= 1) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const prevStepElement = currentStepElement.previousElementSibling;
if (prevStepElement && prevStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in previous step
prevStepElement.classList.add('active');
prevStepElement.style.display = 'block';
prevStepElement.style.opacity = '0';
prevStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
prevStepElement.style.opacity = '1';
prevStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep--;
this.updateProgressBar();
}, 200);
}
}
calculateFinalPrice() {
let total = 0;
// Calculate base price from services
this.selectedServices.forEach(service => {
total += service.price;
});
// Apply complexity multiplier
if (this.selectedComplexity) {
total *= this.selectedComplexity.multiplier;
}
// Apply timeline multiplier
if (this.selectedTimeline) {
total *= this.selectedTimeline.multiplier;
}
// Animate price reveal
const priceElement = document.getElementById('final-price');
if (priceElement) {
priceElement.style.opacity = '0';
priceElement.style.transform = 'scale(0.8)';
setTimeout(() => {
priceElement.textContent = '₩' + total.toLocaleString();
priceElement.style.opacity = '1';
priceElement.style.transform = 'scale(1)';
}, 300);
}
// Generate summary
setTimeout(() => this.generateSummary(total), 600);
}
generateSummary(total) {
const summary = document.getElementById('project-summary');
if (!summary) return;
let summaryHTML = '';
// Services
summaryHTML += '<div class="mb-4"><strong>Selected Services:</strong></div>';
this.selectedServices.forEach(service => {
summaryHTML += `<div class="ml-4 mb-2 flex items-center">
<i class="fas fa-check-circle text-green-500 mr-2"></i>
${this.getServiceName(service.service)}
<span class="ml-auto font-semibold">₩${service.price.toLocaleString()}</span>
</div>`;
});
// Complexity
if (this.selectedComplexity) {
summaryHTML += `<div class="mt-4 mb-2 flex items-center">
<i class="fas fa-layer-group text-blue-500 mr-2"></i>
<strong>Complexity:</strong>
<span class="ml-2">${this.getComplexityName(this.selectedComplexity.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedComplexity.multiplier}</span>
</div>`;
}
// Timeline
if (this.selectedTimeline) {
summaryHTML += `<div class="mb-2 flex items-center">
<i class="fas fa-clock text-purple-500 mr-2"></i>
<strong>Timeline:</strong>
<span class="ml-2">${this.getTimelineName(this.selectedTimeline.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedTimeline.multiplier}</span>
</div>`;
}
// Animate summary appearance
summary.style.opacity = '0';
summary.innerHTML = summaryHTML;
setTimeout(() => {
summary.style.opacity = '1';
}, 200);
}
getServiceName(service) {
const names = {
web: 'Web Development',
mobile: 'Mobile App',
design: 'UI/UX Design',
marketing: 'Digital Marketing'
};
return names[service] || service;
}
getComplexityName(complexity) {
const names = {
simple: 'Simple',
medium: 'Medium',
complex: 'Complex'
};
return names[complexity] || complexity;
}
getTimelineName(timeline) {
const names = {
standard: 'Standard',
rush: 'Rush',
extended: 'Extended'
};
return names[timeline] || timeline;
}
restart() {
// Reset all selections
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
// Reset UI
document.querySelectorAll('.service-option, .complexity-option, .timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Reset steps
document.querySelectorAll('.calculator-step').forEach(step => {
step.classList.remove('active');
step.style.display = 'none';
step.style.opacity = '1';
step.style.transform = 'translateX(0)';
});
// Show first step
const firstStep = document.getElementById('step-1');
if (firstStep) {
firstStep.classList.add('active');
firstStep.style.display = 'block';
}
this.updateProgressBar();
this.updateStepButton();
}
}
// Initialize calculator when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Theme initialization
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.className = theme === 'dark' ? 'dark' : '';
// Initialize calculator
if (document.querySelector('.calculator-step')) {
new ProjectCalculator();
}
});

View File

@@ -0,0 +1,380 @@
// Calculator Logic
class ProjectCalculator {
constructor() {
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
this.totalSteps = 3;
this.init();
}
init() {
this.setupEventListeners();
this.updateProgressBar();
}
setupEventListeners() {
// Service selection
document.querySelectorAll('.service-option').forEach(option => {
option.addEventListener('click', (e) => this.handleServiceSelection(e));
});
// Complexity selection
document.querySelectorAll('.complexity-option').forEach(option => {
option.addEventListener('click', (e) => this.handleComplexitySelection(e));
});
// Timeline selection
document.querySelectorAll('.timeline-option').forEach(option => {
option.addEventListener('click', (e) => this.handleTimelineSelection(e));
});
// Navigation buttons
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', () => this.nextStep());
});
document.querySelectorAll('.prev-step').forEach(btn => {
btn.addEventListener('click', () => this.prevStep());
});
// Restart button
const restartBtn = document.querySelector('.restart-calculator');
if (restartBtn) {
restartBtn.addEventListener('click', () => this.restart());
}
}
handleServiceSelection(e) {
const option = e.currentTarget;
const service = option.dataset.service;
const price = parseInt(option.dataset.basePrice);
option.classList.toggle('selected');
if (option.classList.contains('selected')) {
this.selectedServices.push({ service, price });
this.animateSelection(option, true);
} else {
this.selectedServices = this.selectedServices.filter(s => s.service !== service);
this.animateSelection(option, false);
}
this.updateStepButton();
}
handleComplexitySelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.complexity-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedComplexity = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
handleTimelineSelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedTimeline = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
animateSelection(element, selected) {
if (selected) {
element.style.borderColor = '#3B82F6';
element.style.backgroundColor = '#EBF8FF';
element.style.transform = 'translateY(-2px)';
element.style.boxShadow = '0 8px 25px rgba(59, 130, 246, 0.15)';
} else {
element.style.borderColor = '';
element.style.backgroundColor = '';
element.style.transform = '';
element.style.boxShadow = '';
}
}
updateStepButton() {
const step1Button = document.querySelector('#step-1 .next-step');
const step2Button = document.querySelector('#step-2 .next-step');
if (step1Button) {
step1Button.disabled = this.selectedServices.length === 0;
step1Button.style.opacity = this.selectedServices.length > 0 ? '1' : '0.6';
}
if (step2Button) {
const isValid = this.selectedComplexity && this.selectedTimeline;
step2Button.disabled = !isValid;
step2Button.style.opacity = isValid ? '1' : '0.6';
}
}
updateProgressBar() {
const progressBar = document.querySelector('.calculator-progress-bar');
if (progressBar) {
const progress = (this.currentStep / this.totalSteps) * 100;
progressBar.style.width = `${progress}%`;
progressBar.className = `calculator-progress-bar step-${this.currentStep}`;
}
}
nextStep() {
if (this.currentStep >= this.totalSteps) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const nextStepElement = currentStepElement.nextElementSibling;
if (nextStepElement && nextStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in next step
nextStepElement.classList.add('active');
nextStepElement.style.display = 'block';
nextStepElement.style.opacity = '0';
nextStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
nextStepElement.style.opacity = '1';
nextStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep++;
this.updateProgressBar();
if (this.currentStep === 3) {
setTimeout(() => this.calculateFinalPrice(), 300);
}
}, 200);
}
}
prevStep() {
if (this.currentStep <= 1) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const prevStepElement = currentStepElement.previousElementSibling;
if (prevStepElement && prevStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in previous step
prevStepElement.classList.add('active');
prevStepElement.style.display = 'block';
prevStepElement.style.opacity = '0';
prevStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
prevStepElement.style.opacity = '1';
prevStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep--;
this.updateProgressBar();
}, 200);
}
}
calculateFinalPrice() {
let total = 0;
// Calculate base price from services
this.selectedServices.forEach(service => {
total += service.price;
});
// Apply complexity multiplier
if (this.selectedComplexity) {
total *= this.selectedComplexity.multiplier;
}
// Apply timeline multiplier
if (this.selectedTimeline) {
total *= this.selectedTimeline.multiplier;
}
// Animate price reveal
const priceElement = document.getElementById('final-price');
if (priceElement) {
priceElement.style.opacity = '0';
priceElement.style.transform = 'scale(0.8)';
setTimeout(() => {
priceElement.textContent = '₩' + total.toLocaleString();
priceElement.style.opacity = '1';
priceElement.style.transform = 'scale(1)';
}, 300);
}
// Generate summary
setTimeout(() => this.generateSummary(total), 600);
}
generateSummary(total) {
const summary = document.getElementById('project-summary');
if (!summary) return;
let summaryHTML = '';
// Services
summaryHTML += '<div class="mb-4"><strong>Selected Services:</strong></div>';
this.selectedServices.forEach(service => {
summaryHTML += `<div class="ml-4 mb-2 flex items-center">
<i class="fas fa-check-circle text-green-500 mr-2"></i>
${this.getServiceName(service.service)}
<span class="ml-auto font-semibold">₩${service.price.toLocaleString()}</span>
</div>`;
});
// Complexity
if (this.selectedComplexity) {
summaryHTML += `<div class="mt-4 mb-2 flex items-center">
<i class="fas fa-layer-group text-blue-500 mr-2"></i>
<strong>Complexity:</strong>
<span class="ml-2">${this.getComplexityName(this.selectedComplexity.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedComplexity.multiplier}</span>
</div>`;
}
// Timeline
if (this.selectedTimeline) {
summaryHTML += `<div class="mb-2 flex items-center">
<i class="fas fa-clock text-purple-500 mr-2"></i>
<strong>Timeline:</strong>
<span class="ml-2">${this.getTimelineName(this.selectedTimeline.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedTimeline.multiplier}</span>
</div>`;
}
// Animate summary appearance
summary.style.opacity = '0';
summary.innerHTML = summaryHTML;
setTimeout(() => {
summary.style.opacity = '1';
}, 200);
}
getServiceName(service) {
if (window.calculatorTranslations && window.calculatorTranslations.services) {
return window.calculatorTranslations.services[service] || service;
}
const names = {
web: 'Web Development',
mobile: 'Mobile App',
design: 'UI/UX Design',
marketing: 'Digital Marketing'
};
return names[service] || service;
}
getComplexityName(complexity) {
if (window.calculatorTranslations && window.calculatorTranslations.complexity) {
return window.calculatorTranslations.complexity[complexity] || complexity;
}
const names = {
simple: 'Simple',
medium: 'Medium',
complex: 'Complex'
};
return names[complexity] || complexity;
}
getTimelineName(timeline) {
if (window.calculatorTranslations && window.calculatorTranslations.timeline) {
return window.calculatorTranslations.timeline[timeline] || timeline;
}
const names = {
standard: 'Standard',
rush: 'Rush',
extended: 'Extended'
};
return names[timeline] || timeline;
}
restart() {
// Reset all selections
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
// Reset UI
document.querySelectorAll('.service-option, .complexity-option, .timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Reset steps
document.querySelectorAll('.calculator-step').forEach(step => {
step.classList.remove('active');
step.style.display = 'none';
step.style.opacity = '1';
step.style.transform = 'translateX(0)';
});
// Show first step
const firstStep = document.getElementById('step-1');
if (firstStep) {
firstStep.classList.add('active');
firstStep.style.display = 'block';
}
this.updateProgressBar();
this.updateStepButton();
}
}
// Initialize calculator when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Theme initialization
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.className = theme === 'dark' ? 'dark' : '';
// Initialize calculator
if (document.querySelector('.calculator-step')) {
new ProjectCalculator();
}
});

View File

@@ -0,0 +1,384 @@
// Calculator Logic
class ProjectCalculator {
constructor() {
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
this.totalSteps = 3;
this.init();
}
init() {
this.setupEventListeners();
this.updateProgressBar();
}
setupEventListeners() {
// Service selection
document.querySelectorAll('.service-option').forEach(option => {
option.addEventListener('click', (e) => this.handleServiceSelection(e));
});
// Complexity selection
document.querySelectorAll('.complexity-option').forEach(option => {
option.addEventListener('click', (e) => this.handleComplexitySelection(e));
});
// Timeline selection
document.querySelectorAll('.timeline-option').forEach(option => {
option.addEventListener('click', (e) => this.handleTimelineSelection(e));
});
// Navigation buttons
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', () => this.nextStep());
});
document.querySelectorAll('.prev-step').forEach(btn => {
btn.addEventListener('click', () => this.prevStep());
});
// Restart button
const restartBtn = document.querySelector('.restart-calculator');
if (restartBtn) {
restartBtn.addEventListener('click', () => this.restart());
}
}
handleServiceSelection(e) {
const option = e.currentTarget;
const service = option.dataset.service;
const price = parseInt(option.dataset.basePrice);
option.classList.toggle('selected');
if (option.classList.contains('selected')) {
this.selectedServices.push({ service, price });
this.animateSelection(option, true);
} else {
this.selectedServices = this.selectedServices.filter(s => s.service !== service);
this.animateSelection(option, false);
}
this.updateStepButton();
}
handleComplexitySelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.complexity-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedComplexity = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
handleTimelineSelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedTimeline = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
animateSelection(element, selected) {
if (selected) {
element.style.borderColor = '#3B82F6';
element.style.backgroundColor = '#EBF8FF';
element.style.transform = 'translateY(-2px)';
element.style.boxShadow = '0 8px 25px rgba(59, 130, 246, 0.15)';
} else {
element.style.borderColor = '';
element.style.backgroundColor = '';
element.style.transform = '';
element.style.boxShadow = '';
}
}
updateStepButton() {
const step1Button = document.querySelector('#step-1 .next-step');
const step2Button = document.querySelector('#step-2 .next-step');
if (step1Button) {
step1Button.disabled = this.selectedServices.length === 0;
step1Button.style.opacity = this.selectedServices.length > 0 ? '1' : '0.6';
}
if (step2Button) {
const isValid = this.selectedComplexity && this.selectedTimeline;
step2Button.disabled = !isValid;
step2Button.style.opacity = isValid ? '1' : '0.6';
}
}
updateProgressBar() {
const progressBar = document.querySelector('.calculator-progress-bar');
if (progressBar) {
const progress = (this.currentStep / this.totalSteps) * 100;
progressBar.style.width = `${progress}%`;
progressBar.className = `calculator-progress-bar step-${this.currentStep}`;
}
}
nextStep() {
if (this.currentStep >= this.totalSteps) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const nextStepElement = currentStepElement.nextElementSibling;
if (nextStepElement && nextStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in next step
nextStepElement.classList.add('active');
nextStepElement.style.display = 'block';
nextStepElement.style.opacity = '0';
nextStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
nextStepElement.style.opacity = '1';
nextStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep++;
this.updateProgressBar();
if (this.currentStep === 3) {
setTimeout(() => this.calculateFinalPrice(), 300);
}
}, 200);
}
}
prevStep() {
if (this.currentStep <= 1) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const prevStepElement = currentStepElement.previousElementSibling;
if (prevStepElement && prevStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in previous step
prevStepElement.classList.add('active');
prevStepElement.style.display = 'block';
prevStepElement.style.opacity = '0';
prevStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
prevStepElement.style.opacity = '1';
prevStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep--;
this.updateProgressBar();
}, 200);
}
}
calculateFinalPrice() {
let total = 0;
// Calculate base price from services
this.selectedServices.forEach(service => {
total += service.price;
});
// Apply complexity multiplier
if (this.selectedComplexity) {
total *= this.selectedComplexity.multiplier;
}
// Apply timeline multiplier
if (this.selectedTimeline) {
total *= this.selectedTimeline.multiplier;
}
// Animate price reveal
const priceElement = document.getElementById('final-price');
if (priceElement) {
priceElement.style.opacity = '0';
priceElement.style.transform = 'scale(0.8)';
setTimeout(() => {
priceElement.textContent = '₩' + total.toLocaleString();
priceElement.style.opacity = '1';
priceElement.style.transform = 'scale(1)';
}, 300);
}
// Generate summary
setTimeout(() => this.generateSummary(total), 600);
}
generateSummary(total) {
const summary = document.getElementById('project-summary');
if (!summary) return;
let summaryHTML = '';
// Services
const servicesLabel = window.calculatorTranslations?.labels?.selected_services || 'Selected Services';
const complexityLabel = window.calculatorTranslations?.labels?.complexity || 'Complexity';
const timelineLabel = window.calculatorTranslations?.labels?.timeline || 'Timeline';
summaryHTML += `<div class="mb-4"><strong>${servicesLabel}:</strong></div>`;
this.selectedServices.forEach(service => {
summaryHTML += `<div class="ml-4 mb-2 flex items-center">
<i class="fas fa-check-circle text-green-500 mr-2"></i>
${this.getServiceName(service.service)}
<span class="ml-auto font-semibold">₩${service.price.toLocaleString()}</span>
</div>`;
});
// Complexity
if (this.selectedComplexity) {
summaryHTML += `<div class="mt-4 mb-2 flex items-center">
<i class="fas fa-layer-group text-blue-500 mr-2"></i>
<strong>${complexityLabel}:</strong>
<span class="ml-2">${this.getComplexityName(this.selectedComplexity.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedComplexity.multiplier}</span>
</div>`;
}
// Timeline
if (this.selectedTimeline) {
summaryHTML += `<div class="mb-2 flex items-center">
<i class="fas fa-clock text-purple-500 mr-2"></i>
<strong>${timelineLabel}:</strong>
<span class="ml-2">${this.getTimelineName(this.selectedTimeline.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedTimeline.multiplier}</span>
</div>`;
}
// Animate summary appearance
summary.style.opacity = '0';
summary.innerHTML = summaryHTML;
setTimeout(() => {
summary.style.opacity = '1';
}, 200);
}
getServiceName(service) {
if (window.calculatorTranslations && window.calculatorTranslations.services) {
return window.calculatorTranslations.services[service] || service;
}
const names = {
web: 'Web Development',
mobile: 'Mobile App',
design: 'UI/UX Design',
marketing: 'Digital Marketing'
};
return names[service] || service;
}
getComplexityName(complexity) {
if (window.calculatorTranslations && window.calculatorTranslations.complexity) {
return window.calculatorTranslations.complexity[complexity] || complexity;
}
const names = {
simple: 'Simple',
medium: 'Medium',
complex: 'Complex'
};
return names[complexity] || complexity;
}
getTimelineName(timeline) {
if (window.calculatorTranslations && window.calculatorTranslations.timeline) {
return window.calculatorTranslations.timeline[timeline] || timeline;
}
const names = {
standard: 'Standard',
rush: 'Rush',
extended: 'Extended'
};
return names[timeline] || timeline;
}
restart() {
// Reset all selections
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
// Reset UI
document.querySelectorAll('.service-option, .complexity-option, .timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Reset steps
document.querySelectorAll('.calculator-step').forEach(step => {
step.classList.remove('active');
step.style.display = 'none';
step.style.opacity = '1';
step.style.transform = 'translateX(0)';
});
// Show first step
const firstStep = document.getElementById('step-1');
if (firstStep) {
firstStep.classList.add('active');
firstStep.style.display = 'block';
}
this.updateProgressBar();
this.updateStepButton();
}
}
// Initialize calculator when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Theme initialization
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.className = theme === 'dark' ? 'dark' : '';
// Initialize calculator
if (document.querySelector('.calculator-step')) {
new ProjectCalculator();
}
});

View File

@@ -0,0 +1,384 @@
// Calculator Logic
class ProjectCalculator {
constructor() {
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
this.totalSteps = 3;
this.init();
}
init() {
this.setupEventListeners();
this.updateProgressBar();
}
setupEventListeners() {
// Service selection
document.querySelectorAll('.service-option').forEach(option => {
option.addEventListener('click', (e) => this.handleServiceSelection(e));
});
// Complexity selection
document.querySelectorAll('.complexity-option').forEach(option => {
option.addEventListener('click', (e) => this.handleComplexitySelection(e));
});
// Timeline selection
document.querySelectorAll('.timeline-option').forEach(option => {
option.addEventListener('click', (e) => this.handleTimelineSelection(e));
});
// Navigation buttons
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', () => this.nextStep());
});
document.querySelectorAll('.prev-step').forEach(btn => {
btn.addEventListener('click', () => this.prevStep());
});
// Restart button
const restartBtn = document.querySelector('.restart-calculator');
if (restartBtn) {
restartBtn.addEventListener('click', () => this.restart());
}
}
handleServiceSelection(e) {
const option = e.currentTarget;
const service = option.dataset.service;
const price = parseInt(option.dataset.basePrice);
option.classList.toggle('selected');
if (option.classList.contains('selected')) {
this.selectedServices.push({ service, price });
this.animateSelection(option, true);
} else {
this.selectedServices = this.selectedServices.filter(s => s.service !== service);
this.animateSelection(option, false);
}
this.updateStepButton();
}
handleComplexitySelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.complexity-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedComplexity = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
handleTimelineSelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedTimeline = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
animateSelection(element, selected) {
if (selected) {
element.style.borderColor = '#3B82F6';
element.style.backgroundColor = '#EBF8FF';
element.style.transform = 'translateY(-2px)';
element.style.boxShadow = '0 8px 25px rgba(59, 130, 246, 0.15)';
} else {
element.style.borderColor = '';
element.style.backgroundColor = '';
element.style.transform = '';
element.style.boxShadow = '';
}
}
updateStepButton() {
const step1Button = document.querySelector('#step-1 .next-step');
const step2Button = document.querySelector('#step-2 .next-step');
if (step1Button) {
step1Button.disabled = this.selectedServices.length === 0;
step1Button.style.opacity = this.selectedServices.length > 0 ? '1' : '0.6';
}
if (step2Button) {
const isValid = this.selectedComplexity && this.selectedTimeline;
step2Button.disabled = !isValid;
step2Button.style.opacity = isValid ? '1' : '0.6';
}
}
updateProgressBar() {
const progressBar = document.querySelector('.calculator-progress-bar');
if (progressBar) {
const progress = (this.currentStep / this.totalSteps) * 100;
progressBar.style.width = `${progress}%`;
progressBar.className = `calculator-progress-bar step-${this.currentStep}`;
}
}
nextStep() {
if (this.currentStep >= this.totalSteps) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const nextStepElement = currentStepElement.nextElementSibling;
if (nextStepElement && nextStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in next step
nextStepElement.classList.add('active');
nextStepElement.style.display = 'block';
nextStepElement.style.opacity = '0';
nextStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
nextStepElement.style.opacity = '1';
nextStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep++;
this.updateProgressBar();
if (this.currentStep === 3) {
setTimeout(() => this.calculateFinalPrice(), 300);
}
}, 200);
}
}
prevStep() {
if (this.currentStep <= 1) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const prevStepElement = currentStepElement.previousElementSibling;
if (prevStepElement && prevStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in previous step
prevStepElement.classList.add('active');
prevStepElement.style.display = 'block';
prevStepElement.style.opacity = '0';
prevStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
prevStepElement.style.opacity = '1';
prevStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep--;
this.updateProgressBar();
}, 200);
}
}
calculateFinalPrice() {
let total = 0;
// Calculate base price from services
this.selectedServices.forEach(service => {
total += service.price;
});
// Apply complexity multiplier
if (this.selectedComplexity) {
total *= this.selectedComplexity.multiplier;
}
// Apply timeline multiplier
if (this.selectedTimeline) {
total *= this.selectedTimeline.multiplier;
}
// Animate price reveal
const priceElement = document.getElementById('final-price');
if (priceElement) {
priceElement.style.opacity = '0';
priceElement.style.transform = 'scale(0.8)';
setTimeout(() => {
priceElement.textContent = '₩' + total.toLocaleString();
priceElement.style.opacity = '1';
priceElement.style.transform = 'scale(1)';
}, 300);
}
// Generate summary
setTimeout(() => this.generateSummary(total), 600);
}
generateSummary(total) {
const summary = document.getElementById('project-summary');
if (!summary) return;
let summaryHTML = '';
// Services
const servicesLabel = window.calculatorTranslations?.result?.selected_services || 'Selected Services';
const complexityLabel = window.calculatorTranslations?.result?.complexity || 'Complexity';
const timelineLabel = window.calculatorTranslations?.result?.timeline || 'Timeline';
summaryHTML += `<div class="mb-4"><strong>${servicesLabel}:</strong></div>`;
this.selectedServices.forEach(service => {
summaryHTML += `<div class="ml-4 mb-2 flex items-center">
<i class="fas fa-check-circle text-green-500 mr-2"></i>
${this.getServiceName(service.service)}
<span class="ml-auto font-semibold">₩${service.price.toLocaleString()}</span>
</div>`;
});
// Complexity
if (this.selectedComplexity) {
summaryHTML += `<div class="mt-4 mb-2 flex items-center">
<i class="fas fa-layer-group text-blue-500 mr-2"></i>
<strong>${complexityLabel}:</strong>
<span class="ml-2">${this.getComplexityName(this.selectedComplexity.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedComplexity.multiplier}</span>
</div>`;
}
// Timeline
if (this.selectedTimeline) {
summaryHTML += `<div class="mb-2 flex items-center">
<i class="fas fa-clock text-purple-500 mr-2"></i>
<strong>${timelineLabel}:</strong>
<span class="ml-2">${this.getTimelineName(this.selectedTimeline.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedTimeline.multiplier}</span>
</div>`;
}
// Animate summary appearance
summary.style.opacity = '0';
summary.innerHTML = summaryHTML;
setTimeout(() => {
summary.style.opacity = '1';
}, 200);
}
getServiceName(service) {
if (window.calculatorTranslations && window.calculatorTranslations.services) {
return window.calculatorTranslations.services[service] || service;
}
const names = {
web: 'Web Development',
mobile: 'Mobile App',
design: 'UI/UX Design',
marketing: 'Digital Marketing'
};
return names[service] || service;
}
getComplexityName(complexity) {
if (window.calculatorTranslations && window.calculatorTranslations.complexity) {
return window.calculatorTranslations.complexity[complexity] || complexity;
}
const names = {
simple: 'Simple',
medium: 'Medium',
complex: 'Complex'
};
return names[complexity] || complexity;
}
getTimelineName(timeline) {
if (window.calculatorTranslations && window.calculatorTranslations.timeline) {
return window.calculatorTranslations.timeline[timeline] || timeline;
}
const names = {
standard: 'Standard',
rush: 'Rush',
extended: 'Extended'
};
return names[timeline] || timeline;
}
restart() {
// Reset all selections
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
// Reset UI
document.querySelectorAll('.service-option, .complexity-option, .timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Reset steps
document.querySelectorAll('.calculator-step').forEach(step => {
step.classList.remove('active');
step.style.display = 'none';
step.style.opacity = '1';
step.style.transform = 'translateX(0)';
});
// Show first step
const firstStep = document.getElementById('step-1');
if (firstStep) {
firstStep.classList.add('active');
firstStep.style.display = 'block';
}
this.updateProgressBar();
this.updateStepButton();
}
}
// Initialize calculator when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Theme initialization
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.className = theme === 'dark' ? 'dark' : '';
// Initialize calculator
if (document.querySelector('.calculator-step')) {
new ProjectCalculator();
}
});

View File

@@ -0,0 +1,384 @@
// Calculator Logic
class ProjectCalculator {
constructor() {
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
this.totalSteps = 3;
this.init();
}
init() {
this.setupEventListeners();
this.updateProgressBar();
}
setupEventListeners() {
// Service selection
document.querySelectorAll('.service-option').forEach(option => {
option.addEventListener('click', (e) => this.handleServiceSelection(e));
});
// Complexity selection
document.querySelectorAll('.complexity-option').forEach(option => {
option.addEventListener('click', (e) => this.handleComplexitySelection(e));
});
// Timeline selection
document.querySelectorAll('.timeline-option').forEach(option => {
option.addEventListener('click', (e) => this.handleTimelineSelection(e));
});
// Navigation buttons
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', () => this.nextStep());
});
document.querySelectorAll('.prev-step').forEach(btn => {
btn.addEventListener('click', () => this.prevStep());
});
// Restart button
const restartBtn = document.querySelector('.restart-calculator');
if (restartBtn) {
restartBtn.addEventListener('click', () => this.restart());
}
}
handleServiceSelection(e) {
const option = e.currentTarget;
const service = option.dataset.service;
const price = parseInt(option.dataset.basePrice);
option.classList.toggle('selected');
if (option.classList.contains('selected')) {
this.selectedServices.push({ service, price });
this.animateSelection(option, true);
} else {
this.selectedServices = this.selectedServices.filter(s => s.service !== service);
this.animateSelection(option, false);
}
this.updateStepButton();
}
handleComplexitySelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.complexity-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedComplexity = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
handleTimelineSelection(e) {
const option = e.currentTarget;
// Clear previous selections
document.querySelectorAll('.timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Select current option
option.classList.add('selected');
this.animateSelection(option, true);
this.selectedTimeline = {
value: option.dataset.value,
multiplier: parseFloat(option.dataset.multiplier)
};
this.updateStepButton();
}
animateSelection(element, selected) {
if (selected) {
element.style.borderColor = '#3B82F6';
element.style.backgroundColor = '#EBF8FF';
element.style.transform = 'translateY(-2px)';
element.style.boxShadow = '0 8px 25px rgba(59, 130, 246, 0.15)';
} else {
element.style.borderColor = '';
element.style.backgroundColor = '';
element.style.transform = '';
element.style.boxShadow = '';
}
}
updateStepButton() {
const step1Button = document.querySelector('#step-1 .next-step');
const step2Button = document.querySelector('#step-2 .next-step');
if (step1Button) {
step1Button.disabled = this.selectedServices.length === 0;
step1Button.style.opacity = this.selectedServices.length > 0 ? '1' : '0.6';
}
if (step2Button) {
const isValid = this.selectedComplexity && this.selectedTimeline;
step2Button.disabled = !isValid;
step2Button.style.opacity = isValid ? '1' : '0.6';
}
}
updateProgressBar() {
const progressBar = document.querySelector('.calculator-progress-bar');
if (progressBar) {
const progress = (this.currentStep / this.totalSteps) * 100;
progressBar.style.width = `${progress}%`;
progressBar.className = `calculator-progress-bar step-${this.currentStep}`;
}
}
nextStep() {
if (this.currentStep >= this.totalSteps) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const nextStepElement = currentStepElement.nextElementSibling;
if (nextStepElement && nextStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in next step
nextStepElement.classList.add('active');
nextStepElement.style.display = 'block';
nextStepElement.style.opacity = '0';
nextStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
nextStepElement.style.opacity = '1';
nextStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep++;
this.updateProgressBar();
if (this.currentStep === 3) {
setTimeout(() => this.calculateFinalPrice(), 300);
}
}, 200);
}
}
prevStep() {
if (this.currentStep <= 1) return;
const currentStepElement = document.querySelector('.calculator-step.active');
const prevStepElement = currentStepElement.previousElementSibling;
if (prevStepElement && prevStepElement.classList.contains('calculator-step')) {
// Animate out current step
currentStepElement.style.opacity = '0';
currentStepElement.style.transform = 'translateX(20px)';
setTimeout(() => {
currentStepElement.classList.remove('active');
currentStepElement.style.display = 'none';
// Animate in previous step
prevStepElement.classList.add('active');
prevStepElement.style.display = 'block';
prevStepElement.style.opacity = '0';
prevStepElement.style.transform = 'translateX(-20px)';
setTimeout(() => {
prevStepElement.style.opacity = '1';
prevStepElement.style.transform = 'translateX(0)';
}, 50);
this.currentStep--;
this.updateProgressBar();
}, 200);
}
}
calculateFinalPrice() {
let total = 0;
// Calculate base price from services
this.selectedServices.forEach(service => {
total += service.price;
});
// Apply complexity multiplier
if (this.selectedComplexity) {
total *= this.selectedComplexity.multiplier;
}
// Apply timeline multiplier
if (this.selectedTimeline) {
total *= this.selectedTimeline.multiplier;
}
// Animate price reveal
const priceElement = document.getElementById('final-price');
if (priceElement) {
priceElement.style.opacity = '0';
priceElement.style.transform = 'scale(0.8)';
setTimeout(() => {
priceElement.textContent = '₩' + total.toLocaleString();
priceElement.style.opacity = '1';
priceElement.style.transform = 'scale(1)';
}, 300);
}
// Generate summary
setTimeout(() => this.generateSummary(total), 600);
}
generateSummary(total) {
const summary = document.getElementById('project-summary');
if (!summary) return;
let summaryHTML = '';
// Services
const servicesLabel = window.calculatorTranslations?.result?.selected_services || 'Selected Services';
const complexityLabel = window.calculatorTranslations?.result?.complexity || 'Complexity';
const timelineLabel = window.calculatorTranslations?.result?.timeline || 'Timeline';
summaryHTML += `<div class="mb-4"><strong>${servicesLabel}:</strong></div>`;
this.selectedServices.forEach(service => {
summaryHTML += `<div class="ml-4 mb-2 flex items-center">
<i class="fas fa-check-circle text-green-500 mr-2"></i>
${this.getServiceName(service.service)}
<span class="ml-auto font-semibold">₩${service.price.toLocaleString()}</span>
</div>`;
});
// Complexity
if (this.selectedComplexity) {
summaryHTML += `<div class="mt-4 mb-2 flex items-center">
<i class="fas fa-layer-group text-blue-500 mr-2"></i>
<strong>${complexityLabel}:</strong>
<span class="ml-2">${this.getComplexityName(this.selectedComplexity.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedComplexity.multiplier}</span>
</div>`;
}
// Timeline
if (this.selectedTimeline) {
summaryHTML += `<div class="mb-2 flex items-center">
<i class="fas fa-clock text-purple-500 mr-2"></i>
<strong>${timelineLabel}:</strong>
<span class="ml-2">${this.getTimelineName(this.selectedTimeline.value)}</span>
<span class="ml-auto text-sm text-gray-500">×${this.selectedTimeline.multiplier}</span>
</div>`;
}
// Animate summary appearance
summary.style.opacity = '0';
summary.innerHTML = summaryHTML;
setTimeout(() => {
summary.style.opacity = '1';
}, 200);
}
getServiceName(service) {
if (window.calculatorTranslations && window.calculatorTranslations.services) {
return window.calculatorTranslations.services[service] || service;
}
const names = {
web: 'Web Development',
mobile: 'Mobile App',
design: 'UI/UX Design',
marketing: 'Digital Marketing'
};
return names[service] || service;
}
getComplexityName(complexity) {
if (window.calculatorTranslations && window.calculatorTranslations.complexity) {
return window.calculatorTranslations.complexity[complexity] || complexity;
}
const names = {
simple: 'Simple',
medium: 'Medium',
complex: 'Complex'
};
return names[complexity] || complexity;
}
getTimelineName(timeline) {
if (window.calculatorTranslations && window.calculatorTranslations.timeline) {
return window.calculatorTranslations.timeline[timeline] || timeline;
}
const names = {
standard: 'Standard',
rush: 'Rush',
extended: 'Extended'
};
return names[timeline] || timeline;
}
restart() {
// Reset all selections
this.selectedServices = [];
this.selectedComplexity = null;
this.selectedTimeline = null;
this.currentStep = 1;
// Reset UI
document.querySelectorAll('.service-option, .complexity-option, .timeline-option').forEach(opt => {
opt.classList.remove('selected');
this.animateSelection(opt, false);
});
// Reset steps
document.querySelectorAll('.calculator-step').forEach(step => {
step.classList.remove('active');
step.style.display = 'none';
step.style.opacity = '1';
step.style.transform = 'translateX(0)';
});
// Show first step
const firstStep = document.getElementById('step-1');
if (firstStep) {
firstStep.classList.add('active');
firstStep.style.display = 'block';
}
this.updateProgressBar();
this.updateStepButton();
}
}
// Initialize calculator when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Theme initialization
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.className = theme === 'dark' ? 'dark' : '';
// Initialize calculator
if (document.querySelector('.calculator-step')) {
new ProjectCalculator();
}
});

View File

@@ -0,0 +1,544 @@
// Main JavaScript for SmartSolTech Website
document.addEventListener('DOMContentLoaded', function() {
// Initialize AOS (Animate On Scroll)
if (typeof AOS !== 'undefined') {
AOS.init({
duration: 800,
easing: 'ease-in-out',
once: true,
offset: 100
});
}
// Mobile Navigation Toggle
const mobileMenuButton = document.querySelector('.mobile-menu-button');
const mobileMenu = document.querySelector('.mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('show');
const isOpen = mobileMenu.classList.contains('show');
// Toggle button icon
const icon = mobileMenuButton.querySelector('svg');
if (icon) {
icon.innerHTML = isOpen
? '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />'
: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />';
}
// Accessibility
mobileMenuButton.setAttribute('aria-expanded', isOpen);
});
// Close mobile menu when clicking outside
document.addEventListener('click', function(e) {
if (!mobileMenuButton.contains(e.target) && !mobileMenu.contains(e.target)) {
mobileMenu.classList.remove('show');
mobileMenuButton.setAttribute('aria-expanded', 'false');
}
});
}
// Navbar Scroll Effect
const navbar = document.querySelector('nav');
let lastScrollY = window.scrollY;
window.addEventListener('scroll', function() {
const currentScrollY = window.scrollY;
if (navbar) {
if (currentScrollY > 100) {
navbar.classList.add('navbar-scrolled');
} else {
navbar.classList.remove('navbar-scrolled');
}
// Hide/show navbar on scroll
if (currentScrollY > lastScrollY && currentScrollY > 200) {
navbar.style.transform = 'translateY(-100%)';
} else {
navbar.style.transform = 'translateY(0)';
}
}
lastScrollY = currentScrollY;
});
// Smooth Scrolling for Anchor Links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
const offsetTop = target.offsetTop - (navbar ? navbar.offsetHeight : 0);
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
});
}
});
});
// Contact Form Handler
const quickContactForm = document.getElementById('quick-contact-form');
if (quickContactForm) {
quickContactForm.addEventListener('submit', handleContactSubmit);
}
const mainContactForm = document.getElementById('contact-form');
if (mainContactForm) {
mainContactForm.addEventListener('submit', handleContactSubmit);
}
async function handleContactSubmit(e) {
e.preventDefault();
const form = e.target;
const submitButton = form.querySelector('button[type="submit"]');
const originalText = submitButton.textContent;
// Show loading state
submitButton.disabled = true;
submitButton.classList.add('btn-loading');
submitButton.textContent = '전송 중...';
try {
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
const response = await fetch('/api/contact/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showNotification('메시지가 성공적으로 전송되었습니다! 곧 연락드리겠습니다.', 'success');
form.reset();
} else {
showNotification('메시지 전송 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
}
} catch (error) {
console.error('Contact form error:', error);
showNotification('메시지 전송 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
} finally {
// Reset button state
submitButton.disabled = false;
submitButton.classList.remove('btn-loading');
submitButton.textContent = originalText;
}
}
// Notification System
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<span class="notification-message">${message}</span>
<button class="notification-close" aria-label="Close notification">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
`;
// Styles
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 400px;
padding: 1rem;
border-radius: 0.5rem;
color: white;
font-weight: 500;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
transform: translateX(100%);
transition: transform 0.3s ease;
${type === 'success' ? 'background: #10b981;' : ''}
${type === 'error' ? 'background: #ef4444;' : ''}
${type === 'info' ? 'background: #3b82f6;' : ''}
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// Close button handler
const closeButton = notification.querySelector('.notification-close');
closeButton.addEventListener('click', () => {
closeNotification(notification);
});
// Auto close after 5 seconds
setTimeout(() => {
closeNotification(notification);
}, 5000);
}
function closeNotification(notification) {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}
// Portfolio Filter (if on portfolio page)
const portfolioFilters = document.querySelectorAll('.portfolio-filter');
const portfolioItems = document.querySelectorAll('.portfolio-item');
portfolioFilters.forEach(filter => {
filter.addEventListener('click', function() {
const category = this.dataset.category;
// Update active filter
portfolioFilters.forEach(f => f.classList.remove('active'));
this.classList.add('active');
// Filter items
portfolioItems.forEach(item => {
if (category === 'all' || item.dataset.category === category) {
item.style.display = 'block';
item.style.animation = 'fadeIn 0.5s ease';
} else {
item.style.display = 'none';
}
});
});
});
// Image Lazy Loading
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
// Service Worker Registration for PWA
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered successfully:', registration);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
}
// Performance Monitoring
if ('performance' in window) {
window.addEventListener('load', () => {
setTimeout(() => {
const perfData = performance.getEntriesByType('navigation')[0];
const loadTime = perfData.loadEventEnd - perfData.loadEventStart;
if (loadTime > 3000) {
console.warn('Page load time is slow:', loadTime + 'ms');
}
}, 1000);
});
}
// Cookie Consent (if needed)
function initCookieConsent() {
const consent = localStorage.getItem('cookieConsent');
if (!consent) {
showCookieConsent();
}
}
function showCookieConsent() {
const banner = document.createElement('div');
banner.className = 'cookie-consent';
banner.innerHTML = `
<div class="cookie-content">
<p>이 웹사이트는 더 나은 서비스 제공을 위해 쿠키를 사용합니다.</p>
<div class="cookie-buttons">
<button id="accept-cookies" class="btn btn-primary">동의</button>
<button id="decline-cookies" class="btn btn-secondary">거부</button>
</div>
</div>
`;
banner.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 1rem;
z-index: 9999;
transform: translateY(100%);
transition: transform 0.3s ease;
`;
document.body.appendChild(banner);
setTimeout(() => {
banner.style.transform = 'translateY(0)';
}, 100);
document.getElementById('accept-cookies').addEventListener('click', () => {
localStorage.setItem('cookieConsent', 'accepted');
banner.style.transform = 'translateY(100%)';
setTimeout(() => banner.remove(), 300);
});
document.getElementById('decline-cookies').addEventListener('click', () => {
localStorage.setItem('cookieConsent', 'declined');
banner.style.transform = 'translateY(100%)';
setTimeout(() => banner.remove(), 300);
});
}
// Initialize cookie consent
// initCookieConsent();
// Parallax Effect
const parallaxElements = document.querySelectorAll('.parallax');
function updateParallax() {
const scrollY = window.pageYOffset;
parallaxElements.forEach(element => {
const speed = element.dataset.speed || 0.5;
const yPos = -(scrollY * speed);
element.style.transform = `translateY(${yPos}px)`;
});
}
if (parallaxElements.length > 0) {
window.addEventListener('scroll', updateParallax);
}
// Counter Animation
const counters = document.querySelectorAll('.counter');
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateCounter(entry.target);
counterObserver.unobserve(entry.target);
}
});
});
counters.forEach(counter => counterObserver.observe(counter));
function animateCounter(element) {
const target = parseInt(element.dataset.count);
const duration = 2000;
const start = performance.now();
function updateCounter(currentTime) {
const elapsed = currentTime - start;
const progress = Math.min(elapsed / duration, 1);
const current = Math.floor(progress * target);
element.textContent = current.toLocaleString();
if (progress < 1) {
requestAnimationFrame(updateCounter);
}
}
requestAnimationFrame(updateCounter);
}
// Typing Effect for Hero Text
const typingElements = document.querySelectorAll('.typing-effect');
typingElements.forEach(element => {
const text = element.textContent;
element.textContent = '';
element.style.borderRight = '2px solid';
let i = 0;
const timer = setInterval(() => {
element.textContent += text[i];
i++;
if (i >= text.length) {
clearInterval(timer);
element.style.borderRight = 'none';
}
}, 100);
});
// Handle form validation
const forms = document.querySelectorAll('form[data-validate]');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
if (!validateForm(this)) {
e.preventDefault();
}
});
// Real-time validation
const inputs = form.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('blur', () => validateField(input));
input.addEventListener('input', () => clearFieldError(input));
});
});
function validateForm(form) {
let isValid = true;
const inputs = form.querySelectorAll('input[required], textarea[required]');
inputs.forEach(input => {
if (!validateField(input)) {
isValid = false;
}
});
return isValid;
}
function validateField(field) {
const value = field.value.trim();
const type = field.type;
let isValid = true;
let message = '';
// Required field check
if (field.hasAttribute('required') && !value) {
isValid = false;
message = '이 필드는 필수입니다.';
}
// Email validation
if (type === 'email' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
isValid = false;
message = '올바른 이메일 형식을 입력해주세요.';
}
}
// Phone validation
if (type === 'tel' && value) {
const phoneRegex = /^[0-9-+\s()]+$/;
if (!phoneRegex.test(value)) {
isValid = false;
message = '올바른 전화번호 형식을 입력해주세요.';
}
}
// Show/hide error
if (!isValid) {
showFieldError(field, message);
} else {
clearFieldError(field);
}
return isValid;
}
function showFieldError(field, message) {
clearFieldError(field);
field.classList.add('error');
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.textContent = message;
errorDiv.style.cssText = 'color: #ef4444; font-size: 0.875rem; margin-top: 0.25rem;';
field.parentNode.appendChild(errorDiv);
}
function clearFieldError(field) {
field.classList.remove('error');
const errorDiv = field.parentNode.querySelector('.field-error');
if (errorDiv) {
errorDiv.remove();
}
}
});
// Utility Functions
const utils = {
// Debounce function
debounce: function(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// Throttle function
throttle: function(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// Format currency
formatCurrency: function(amount, currency = 'KRW') {
return new Intl.NumberFormat('ko-KR', {
style: 'currency',
currency: currency,
minimumFractionDigits: 0
}).format(amount);
},
// Format date
formatDate: function(date, options = {}) {
const defaultOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
return new Intl.DateTimeFormat('ko-KR', { ...defaultOptions, ...options }).format(new Date(date));
}
};
// Global error handler
window.addEventListener('error', function(e) {
console.error('Global error:', e.error);
// Could send error to analytics service
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(e) {
console.error('Unhandled promise rejection:', e.reason);
// Could send error to analytics service
});
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = utils;
}

View File

@@ -0,0 +1,544 @@
// Main JavaScript for SmartSolTech Website
document.addEventListener('DOMContentLoaded', function() {
// Initialize AOS (Animate On Scroll)
if (typeof AOS !== 'undefined') {
AOS.init({
duration: 800,
easing: 'ease-in-out',
once: true,
offset: 100
});
}
// Mobile Navigation Toggle
const mobileMenuButton = document.querySelector('.mobile-menu-button');
const mobileMenu = document.querySelector('.mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('show');
const isOpen = mobileMenu.classList.contains('show');
// Toggle button icon
const icon = mobileMenuButton.querySelector('svg');
if (icon) {
icon.innerHTML = isOpen
? '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />'
: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />';
}
// Accessibility
mobileMenuButton.setAttribute('aria-expanded', isOpen);
});
// Close mobile menu when clicking outside
document.addEventListener('click', function(e) {
if (!mobileMenuButton.contains(e.target) && !mobileMenu.contains(e.target)) {
mobileMenu.classList.remove('show');
mobileMenuButton.setAttribute('aria-expanded', 'false');
}
});
}
// Navbar Scroll Effect
const navbar = document.querySelector('nav');
let lastScrollY = window.scrollY;
window.addEventListener('scroll', function() {
const currentScrollY = window.scrollY;
if (navbar) {
if (currentScrollY > 100) {
navbar.classList.add('navbar-scrolled');
} else {
navbar.classList.remove('navbar-scrolled');
}
// Hide/show navbar on scroll
if (currentScrollY > lastScrollY && currentScrollY > 200) {
navbar.style.transform = 'translateY(-100%)';
} else {
navbar.style.transform = 'translateY(0)';
}
}
lastScrollY = currentScrollY;
});
// Smooth Scrolling for Anchor Links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
const offsetTop = target.offsetTop - (navbar ? navbar.offsetHeight : 0);
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
});
}
});
});
// Contact Form Handler
const quickContactForm = document.getElementById('quick-contact-form');
if (quickContactForm) {
quickContactForm.addEventListener('submit', handleContactSubmit);
}
const mainContactForm = document.getElementById('contact-form');
if (mainContactForm) {
mainContactForm.addEventListener('submit', handleContactSubmit);
}
async function handleContactSubmit(e) {
e.preventDefault();
const form = e.target;
const submitButton = form.querySelector('button[type="submit"]');
const originalText = submitButton.textContent;
// Show loading state
submitButton.disabled = true;
submitButton.classList.add('btn-loading');
submitButton.textContent = '전송 중...';
try {
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
const response = await fetch('/api/contact/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showNotification('메시지가 성공적으로 전송되었습니다! 곧 연락드리겠습니다.', 'success');
form.reset();
} else {
showNotification('메시지 전송 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
}
} catch (error) {
console.error('Contact form error:', error);
showNotification('메시지 전송 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
} finally {
// Reset button state
submitButton.disabled = false;
submitButton.classList.remove('btn-loading');
submitButton.textContent = originalText;
}
}
// Notification System
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<span class="notification-message">${message}</span>
<button class="notification-close" aria-label="Close notification">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
`;
// Styles
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 400px;
padding: 1rem;
border-radius: 0.5rem;
color: white;
font-weight: 500;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
transform: translateX(100%);
transition: transform 0.3s ease;
${type === 'success' ? 'background: #10b981;' : ''}
${type === 'error' ? 'background: #ef4444;' : ''}
${type === 'info' ? 'background: #3b82f6;' : ''}
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// Close button handler
const closeButton = notification.querySelector('.notification-close');
closeButton.addEventListener('click', () => {
closeNotification(notification);
});
// Auto close after 5 seconds
setTimeout(() => {
closeNotification(notification);
}, 5000);
}
function closeNotification(notification) {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}
// Portfolio Filter (if on portfolio page)
const portfolioFilters = document.querySelectorAll('.portfolio-filter');
const portfolioItems = document.querySelectorAll('.portfolio-item');
portfolioFilters.forEach(filter => {
filter.addEventListener('click', function() {
const category = this.dataset.category;
// Update active filter
portfolioFilters.forEach(f => f.classList.remove('active'));
this.classList.add('active');
// Filter items
portfolioItems.forEach(item => {
if (category === 'all' || item.dataset.category === category) {
item.style.display = 'block';
item.style.animation = 'fadeIn 0.5s ease';
} else {
item.style.display = 'none';
}
});
});
});
// Image Lazy Loading
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
// Service Worker Registration for PWA
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered successfully:', registration);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
}
// Performance Monitoring
if ('performance' in window) {
window.addEventListener('load', () => {
setTimeout(() => {
const perfData = performance.getEntriesByType('navigation')[0];
const loadTime = perfData.loadEventEnd - perfData.loadEventStart;
if (loadTime > 3000) {
console.warn('Page load time is slow:', loadTime + 'ms');
}
}, 1000);
});
}
// Cookie Consent (if needed)
function initCookieConsent() {
const consent = localStorage.getItem('cookieConsent');
if (!consent) {
showCookieConsent();
}
}
function showCookieConsent() {
const banner = document.createElement('div');
banner.className = 'cookie-consent';
banner.innerHTML = `
<div class="cookie-content">
<p>이 웹사이트는 더 나은 서비스 제공을 위해 쿠키를 사용합니다.</p>
<div class="cookie-buttons">
<button id="accept-cookies" class="btn btn-primary">동의</button>
<button id="decline-cookies" class="btn btn-secondary">거부</button>
</div>
</div>
`;
banner.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 1rem;
z-index: 9999;
transform: translateY(100%);
transition: transform 0.3s ease;
`;
document.body.appendChild(banner);
setTimeout(() => {
banner.style.transform = 'translateY(0)';
}, 100);
document.getElementById('accept-cookies').addEventListener('click', () => {
localStorage.setItem('cookieConsent', 'accepted');
banner.style.transform = 'translateY(100%)';
setTimeout(() => banner.remove(), 300);
});
document.getElementById('decline-cookies').addEventListener('click', () => {
localStorage.setItem('cookieConsent', 'declined');
banner.style.transform = 'translateY(100%)';
setTimeout(() => banner.remove(), 300);
});
}
// Initialize cookie consent
// initCookieConsent();
// Parallax Effect
const parallaxElements = document.querySelectorAll('.parallax');
function updateParallax() {
const scrollY = window.pageYOffset;
parallaxElements.forEach(element => {
const speed = element.dataset.speed || 0.5;
const yPos = -(scrollY * speed);
element.style.transform = `translateY(${yPos}px)`;
});
}
if (parallaxElements.length > 0) {
window.addEventListener('scroll', updateParallax);
}
// Counter Animation
const counters = document.querySelectorAll('.counter');
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateCounter(entry.target);
counterObserver.unobserve(entry.target);
}
});
});
counters.forEach(counter => counterObserver.observe(counter));
function animateCounter(element) {
const target = parseInt(element.dataset.count);
const duration = 2000;
const start = performance.now();
function updateCounter(currentTime) {
const elapsed = currentTime - start;
const progress = Math.min(elapsed / duration, 1);
const current = Math.floor(progress * target);
element.textContent = current.toLocaleString();
if (progress < 1) {
requestAnimationFrame(updateCounter);
}
}
requestAnimationFrame(updateCounter);
}
// Typing Effect for Hero Text
const typingElements = document.querySelectorAll('.typing-effect');
typingElements.forEach(element => {
const text = element.textContent;
element.textContent = '';
element.style.borderRight = '2px solid';
let i = 0;
const timer = setInterval(() => {
element.textContent += text[i];
i++;
if (i >= text.length) {
clearInterval(timer);
element.style.borderRight = 'none';
}
}, 100);
});
// Handle form validation
const forms = document.querySelectorAll('form[data-validate]');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
if (!validateForm(this)) {
e.preventDefault();
}
});
// Real-time validation
const inputs = form.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('blur', () => validateField(input));
input.addEventListener('input', () => clearFieldError(input));
});
});
function validateForm(form) {
let isValid = true;
const inputs = form.querySelectorAll('input[required], textarea[required]');
inputs.forEach(input => {
if (!validateField(input)) {
isValid = false;
}
});
return isValid;
}
function validateField(field) {
const value = field.value.trim();
const type = field.type;
let isValid = true;
let message = '';
// Required field check
if (field.hasAttribute('required') && !value) {
isValid = false;
message = '이 필드는 필수입니다.';
}
// Email validation
if (type === 'email' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
isValid = false;
message = '올바른 이메일 형식을 입력해주세요.';
}
}
// Phone validation
if (type === 'tel' && value) {
const phoneRegex = /^[0-9-+\s()]+$/;
if (!phoneRegex.test(value)) {
isValid = false;
message = '올바른 전화번호 형식을 입력해주세요.';
}
}
// Show/hide error
if (!isValid) {
showFieldError(field, message);
} else {
clearFieldError(field);
}
return isValid;
}
function showFieldError(field, message) {
clearFieldError(field);
field.classList.add('error');
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.textContent = message;
errorDiv.style.cssText = 'color: #ef4444; font-size: 0.875rem; margin-top: 0.25rem;';
field.parentNode.appendChild(errorDiv);
}
function clearFieldError(field) {
field.classList.remove('error');
const errorDiv = field.parentNode.querySelector('.field-error');
if (errorDiv) {
errorDiv.remove();
}
}
});
// Utility Functions
const utils = {
// Debounce function
debounce: function(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// Throttle function
throttle: function(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// Format currency
formatCurrency: function(amount, currency = 'KRW') {
return new Intl.NumberFormat('ko-KR', {
style: 'currency',
currency: currency,
minimumFractionDigits: 0
}).format(amount);
},
// Format date
formatDate: function(date, options = {}) {
const defaultOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
return new Intl.DateTimeFormat('ko-KR', { ...defaultOptions, ...options }).format(new Date(date));
}
};
// Global error handler
window.addEventListener('error', function(e) {
console.error('Global error:', e.error);
// Could send error to analytics service
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(e) {
console.error('Unhandled promise rejection:', e.reason);
// Could send error to analytics service
});
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = utils;
}

View File

@@ -0,0 +1,161 @@
{
"name": "SmartSolTech - Technology Solutions",
"short_name": "SmartSolTech",
"description": "Professional web development, mobile apps, and digital solutions in Korea",
"start_url": "/",
"display": "standalone",
"orientation": "portrait-primary",
"theme_color": "#3b82f6",
"background_color": "#ffffff",
"lang": "ko",
"scope": "/",
"categories": ["business", "productivity", "technology"],
"icons": [
{
"src": "/images/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"screenshots": [
{
"src": "/images/screenshot-desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide",
"label": "SmartSolTech Desktop View"
},
{
"src": "/images/screenshot-mobile.png",
"sizes": "375x812",
"type": "image/png",
"form_factor": "narrow",
"label": "SmartSolTech Mobile View"
}
],
"shortcuts": [
{
"name": "Portfolio",
"short_name": "Portfolio",
"description": "View our latest projects",
"url": "/portfolio",
"icons": [
{
"src": "/images/shortcut-portfolio.png",
"sizes": "96x96",
"type": "image/png"
}
]
},
{
"name": "Calculator",
"short_name": "Calculator",
"description": "Calculate project costs",
"url": "/calculator",
"icons": [
{
"src": "/images/shortcut-calculator.png",
"sizes": "96x96",
"type": "image/png"
}
]
},
{
"name": "Contact",
"short_name": "Contact",
"description": "Get in touch with us",
"url": "/contact",
"icons": [
{
"src": "/images/shortcut-contact.png",
"sizes": "96x96",
"type": "image/png"
}
]
}
],
"related_applications": [
{
"platform": "webapp",
"url": "https://smartsoltech.kr/manifest.json"
}
],
"prefer_related_applications": false,
"edge_side_panel": {
"preferred_width": 400
},
"protocol_handlers": [
{
"protocol": "mailto",
"url": "/contact?email=%s"
}
],
"file_handlers": [
{
"action": "/open-file",
"accept": {
"image/*": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
"application/pdf": [".pdf"]
}
}
],
"share_target": {
"action": "/share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [
{
"name": "images",
"accept": ["image/*"]
}
]
}
}
}

View File

@@ -0,0 +1,161 @@
{
"name": "SmartSolTech - Technology Solutions",
"short_name": "SmartSolTech",
"description": "Professional web development, mobile apps, and digital solutions in Korea",
"start_url": "/",
"display": "standalone",
"orientation": "portrait-primary",
"theme_color": "#3b82f6",
"background_color": "#ffffff",
"lang": "ko",
"scope": "/",
"categories": ["business", "productivity", "technology"],
"icons": [
{
"src": "/images/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"screenshots": [
{
"src": "/images/screenshot-desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide",
"label": "SmartSolTech Desktop View"
},
{
"src": "/images/screenshot-mobile.png",
"sizes": "375x812",
"type": "image/png",
"form_factor": "narrow",
"label": "SmartSolTech Mobile View"
}
],
"shortcuts": [
{
"name": "Portfolio",
"short_name": "Portfolio",
"description": "View our latest projects",
"url": "/portfolio",
"icons": [
{
"src": "/images/shortcut-portfolio.png",
"sizes": "96x96",
"type": "image/png"
}
]
},
{
"name": "Calculator",
"short_name": "Calculator",
"description": "Calculate project costs",
"url": "/calculator",
"icons": [
{
"src": "/images/shortcut-calculator.png",
"sizes": "96x96",
"type": "image/png"
}
]
},
{
"name": "Contact",
"short_name": "Contact",
"description": "Get in touch with us",
"url": "/contact",
"icons": [
{
"src": "/images/shortcut-contact.png",
"sizes": "96x96",
"type": "image/png"
}
]
}
],
"related_applications": [
{
"platform": "webapp",
"url": "https://smartsoltech.kr/manifest.json"
}
],
"prefer_related_applications": false,
"edge_side_panel": {
"preferred_width": 400
},
"protocol_handlers": [
{
"protocol": "mailto",
"url": "/contact?email=%s"
}
],
"file_handlers": [
{
"action": "/open-file",
"accept": {
"image/*": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
"application/pdf": [".pdf"]
}
}
],
"share_target": {
"action": "/share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [
{
"name": "images",
"accept": ["image/*"]
}
]
}
}
}

View File

@@ -0,0 +1,396 @@
// Service Worker for SmartSolTech PWA
const CACHE_NAME = 'smartsoltech-v1.0.0';
const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.0';
const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.0';
// Files to cache immediately
const STATIC_FILES = [
'/',
'/css/main.css',
'/js/main.js',
'/images/logo.png',
'/images/icon-192x192.png',
'/images/icon-512x512.png',
'/manifest.json',
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap',
'https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css',
'https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.js'
];
// Routes to cache dynamically
const DYNAMIC_ROUTES = [
'/about',
'/services',
'/portfolio',
'/calculator',
'/contact'
];
// API endpoints to cache
const API_CACHE_PATTERNS = [
/^\/api\/portfolio/,
/^\/api\/services/,
/^\/api\/calculator\/services/
];
// Install event - cache static files
self.addEventListener('install', event => {
console.log('Service Worker: Installing...');
event.waitUntil(
caches.open(STATIC_CACHE_NAME)
.then(cache => {
console.log('Service Worker: Caching static files');
return cache.addAll(STATIC_FILES);
})
.then(() => {
console.log('Service Worker: Static files cached');
return self.skipWaiting();
})
.catch(error => {
console.error('Service Worker: Error caching static files', error);
})
);
});
// Activate event - clean up old caches
self.addEventListener('activate', event => {
console.log('Service Worker: Activating...');
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== STATIC_CACHE_NAME &&
cacheName !== DYNAMIC_CACHE_NAME) {
console.log('Service Worker: Deleting old cache', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(() => {
console.log('Service Worker: Activated');
return self.clients.claim();
})
);
});
// Fetch event - serve cached files or fetch from network
self.addEventListener('fetch', event => {
const request = event.request;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip Chrome extension requests
if (url.protocol === 'chrome-extension:') {
return;
}
// Handle different types of requests
if (isStaticFile(request.url)) {
event.respondWith(cacheFirst(request));
} else if (isAPIRequest(request.url)) {
event.respondWith(networkFirst(request));
} else if (isDynamicRoute(request.url)) {
event.respondWith(staleWhileRevalidate(request));
} else {
event.respondWith(networkFirst(request));
}
});
// Cache strategies
async function cacheFirst(request) {
try {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
const cache = await caches.open(STATIC_CACHE_NAME);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
console.error('Cache first strategy failed:', error);
return new Response('Offline', { status: 503 });
}
}
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
// Cache successful responses
if (networkResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.log('Network first: Falling back to cache for', request.url);
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Return offline page for navigation requests
if (request.mode === 'navigate') {
return caches.match('/offline.html') || new Response('Offline', {
status: 503,
headers: { 'Content-Type': 'text/html' }
});
}
return new Response('Network Error', { status: 503 });
}
}
async function staleWhileRevalidate(request) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
const cachedResponse = await cache.match(request);
const fetchPromise = fetch(request).then(networkResponse => {
if (networkResponse.ok) {
cache.put(request, networkResponse.clone());
}
return networkResponse;
});
return cachedResponse || fetchPromise;
}
// Helper functions
function isStaticFile(url) {
return url.includes('/css/') ||
url.includes('/js/') ||
url.includes('/images/') ||
url.includes('/fonts/') ||
url.includes('googleapis.com') ||
url.includes('cdnjs.cloudflare.com');
}
function isAPIRequest(url) {
return url.includes('/api/') ||
API_CACHE_PATTERNS.some(pattern => pattern.test(url));
}
function isDynamicRoute(url) {
const pathname = new URL(url).pathname;
return DYNAMIC_ROUTES.includes(pathname) ||
pathname.startsWith('/portfolio/') ||
pathname.startsWith('/services/');
}
// Background sync for form submissions
self.addEventListener('sync', event => {
console.log('Service Worker: Background sync triggered', event.tag);
if (event.tag === 'contact-form-sync') {
event.waitUntil(syncContactForms());
}
});
async function syncContactForms() {
try {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
const requests = await cache.keys();
const contactRequests = requests.filter(request =>
request.url.includes('/api/contact/submit')
);
for (const request of contactRequests) {
try {
await fetch(request);
await cache.delete(request);
console.log('Contact form synced successfully');
} catch (error) {
console.error('Failed to sync contact form:', error);
}
}
} catch (error) {
console.error('Background sync failed:', error);
}
}
// Push notification handling
self.addEventListener('push', event => {
console.log('Service Worker: Push received', event);
let data = {};
if (event.data) {
data = event.data.json();
}
const title = data.title || 'SmartSolTech';
const options = {
body: data.body || 'You have a new notification',
icon: '/images/icon-192x192.png',
badge: '/images/icon-72x72.png',
tag: data.tag || 'default',
data: data.url || '/',
actions: [
{
action: 'open',
title: '열기',
icon: '/images/icon-open.png'
},
{
action: 'close',
title: '닫기',
icon: '/images/icon-close.png'
}
],
requireInteraction: data.requireInteraction || false,
silent: data.silent || false,
vibrate: data.vibrate || [200, 100, 200]
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
// Notification click handling
self.addEventListener('notificationclick', event => {
console.log('Service Worker: Notification clicked', event);
event.notification.close();
if (event.action === 'close') {
return;
}
const url = event.notification.data || '/';
event.waitUntil(
clients.matchAll({ type: 'window' }).then(clientList => {
// Check if window is already open
for (const client of clientList) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// Open new window
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
// Handle messages from main thread
self.addEventListener('message', event => {
console.log('Service Worker: Message received', event.data);
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'CACHE_URLS') {
cacheUrls(event.data.urls);
}
});
async function cacheUrls(urls) {
try {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
await cache.addAll(urls);
console.log('URLs cached successfully:', urls);
} catch (error) {
console.error('Failed to cache URLs:', error);
}
}
// Periodic background sync (if supported)
self.addEventListener('periodicsync', event => {
console.log('Service Worker: Periodic sync triggered', event.tag);
if (event.tag === 'content-sync') {
event.waitUntil(syncContent());
}
});
async function syncContent() {
try {
// Fetch fresh portfolio and services data
const portfolioResponse = await fetch('/api/portfolio?featured=true');
const servicesResponse = await fetch('/api/services?featured=true');
if (portfolioResponse.ok && servicesResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
cache.put('/api/portfolio?featured=true', portfolioResponse.clone());
cache.put('/api/services?featured=true', servicesResponse.clone());
console.log('Content synced successfully');
}
} catch (error) {
console.error('Content sync failed:', error);
}
}
// Cache management utilities
async function cleanupCaches() {
const cacheNames = await caches.keys();
const currentCaches = [STATIC_CACHE_NAME, DYNAMIC_CACHE_NAME];
return Promise.all(
cacheNames.map(cacheName => {
if (!currentCaches.includes(cacheName)) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}
// Limit cache size
async function limitCacheSize(cacheName, maxItems) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxItems) {
const keysToDelete = keys.slice(0, keys.length - maxItems);
return Promise.all(keysToDelete.map(key => cache.delete(key)));
}
}
// Performance monitoring
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
const start = performance.now();
event.respondWith(
fetch(event.request).then(response => {
const duration = performance.now() - start;
// Log slow API requests
if (duration > 2000) {
console.warn('Slow API request:', event.request.url, duration + 'ms');
}
return response;
})
);
}
});
// Error tracking
self.addEventListener('error', event => {
console.error('Service Worker error:', event.error);
// Could send to analytics service
});
self.addEventListener('unhandledrejection', event => {
console.error('Service Worker unhandled rejection:', event.reason);
// Could send to analytics service
});

View File

@@ -0,0 +1,396 @@
// Service Worker for SmartSolTech PWA
const CACHE_NAME = 'smartsoltech-v1.0.0';
const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.0';
const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.0';
// Files to cache immediately
const STATIC_FILES = [
'/',
'/css/main.css',
'/js/main.js',
'/images/logo.png',
'/images/icon-192x192.png',
'/images/icon-512x512.png',
'/manifest.json',
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap',
'https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css',
'https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.js'
];
// Routes to cache dynamically
const DYNAMIC_ROUTES = [
'/about',
'/services',
'/portfolio',
'/calculator',
'/contact'
];
// API endpoints to cache
const API_CACHE_PATTERNS = [
/^\/api\/portfolio/,
/^\/api\/services/,
/^\/api\/calculator\/services/
];
// Install event - cache static files
self.addEventListener('install', event => {
console.log('Service Worker: Installing...');
event.waitUntil(
caches.open(STATIC_CACHE_NAME)
.then(cache => {
console.log('Service Worker: Caching static files');
return cache.addAll(STATIC_FILES);
})
.then(() => {
console.log('Service Worker: Static files cached');
return self.skipWaiting();
})
.catch(error => {
console.error('Service Worker: Error caching static files', error);
})
);
});
// Activate event - clean up old caches
self.addEventListener('activate', event => {
console.log('Service Worker: Activating...');
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== STATIC_CACHE_NAME &&
cacheName !== DYNAMIC_CACHE_NAME) {
console.log('Service Worker: Deleting old cache', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(() => {
console.log('Service Worker: Activated');
return self.clients.claim();
})
);
});
// Fetch event - serve cached files or fetch from network
self.addEventListener('fetch', event => {
const request = event.request;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip Chrome extension requests
if (url.protocol === 'chrome-extension:') {
return;
}
// Handle different types of requests
if (isStaticFile(request.url)) {
event.respondWith(cacheFirst(request));
} else if (isAPIRequest(request.url)) {
event.respondWith(networkFirst(request));
} else if (isDynamicRoute(request.url)) {
event.respondWith(staleWhileRevalidate(request));
} else {
event.respondWith(networkFirst(request));
}
});
// Cache strategies
async function cacheFirst(request) {
try {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
const cache = await caches.open(STATIC_CACHE_NAME);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
console.error('Cache first strategy failed:', error);
return new Response('Offline', { status: 503 });
}
}
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
// Cache successful responses
if (networkResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.log('Network first: Falling back to cache for', request.url);
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Return offline page for navigation requests
if (request.mode === 'navigate') {
return caches.match('/offline.html') || new Response('Offline', {
status: 503,
headers: { 'Content-Type': 'text/html' }
});
}
return new Response('Network Error', { status: 503 });
}
}
async function staleWhileRevalidate(request) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
const cachedResponse = await cache.match(request);
const fetchPromise = fetch(request).then(networkResponse => {
if (networkResponse.ok) {
cache.put(request, networkResponse.clone());
}
return networkResponse;
});
return cachedResponse || fetchPromise;
}
// Helper functions
function isStaticFile(url) {
return url.includes('/css/') ||
url.includes('/js/') ||
url.includes('/images/') ||
url.includes('/fonts/') ||
url.includes('googleapis.com') ||
url.includes('cdnjs.cloudflare.com');
}
function isAPIRequest(url) {
return url.includes('/api/') ||
API_CACHE_PATTERNS.some(pattern => pattern.test(url));
}
function isDynamicRoute(url) {
const pathname = new URL(url).pathname;
return DYNAMIC_ROUTES.includes(pathname) ||
pathname.startsWith('/portfolio/') ||
pathname.startsWith('/services/');
}
// Background sync for form submissions
self.addEventListener('sync', event => {
console.log('Service Worker: Background sync triggered', event.tag);
if (event.tag === 'contact-form-sync') {
event.waitUntil(syncContactForms());
}
});
async function syncContactForms() {
try {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
const requests = await cache.keys();
const contactRequests = requests.filter(request =>
request.url.includes('/api/contact/submit')
);
for (const request of contactRequests) {
try {
await fetch(request);
await cache.delete(request);
console.log('Contact form synced successfully');
} catch (error) {
console.error('Failed to sync contact form:', error);
}
}
} catch (error) {
console.error('Background sync failed:', error);
}
}
// Push notification handling
self.addEventListener('push', event => {
console.log('Service Worker: Push received', event);
let data = {};
if (event.data) {
data = event.data.json();
}
const title = data.title || 'SmartSolTech';
const options = {
body: data.body || 'You have a new notification',
icon: '/images/icon-192x192.png',
badge: '/images/icon-72x72.png',
tag: data.tag || 'default',
data: data.url || '/',
actions: [
{
action: 'open',
title: '열기',
icon: '/images/icon-open.png'
},
{
action: 'close',
title: '닫기',
icon: '/images/icon-close.png'
}
],
requireInteraction: data.requireInteraction || false,
silent: data.silent || false,
vibrate: data.vibrate || [200, 100, 200]
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
// Notification click handling
self.addEventListener('notificationclick', event => {
console.log('Service Worker: Notification clicked', event);
event.notification.close();
if (event.action === 'close') {
return;
}
const url = event.notification.data || '/';
event.waitUntil(
clients.matchAll({ type: 'window' }).then(clientList => {
// Check if window is already open
for (const client of clientList) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// Open new window
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
// Handle messages from main thread
self.addEventListener('message', event => {
console.log('Service Worker: Message received', event.data);
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'CACHE_URLS') {
cacheUrls(event.data.urls);
}
});
async function cacheUrls(urls) {
try {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
await cache.addAll(urls);
console.log('URLs cached successfully:', urls);
} catch (error) {
console.error('Failed to cache URLs:', error);
}
}
// Periodic background sync (if supported)
self.addEventListener('periodicsync', event => {
console.log('Service Worker: Periodic sync triggered', event.tag);
if (event.tag === 'content-sync') {
event.waitUntil(syncContent());
}
});
async function syncContent() {
try {
// Fetch fresh portfolio and services data
const portfolioResponse = await fetch('/api/portfolio?featured=true');
const servicesResponse = await fetch('/api/services?featured=true');
if (portfolioResponse.ok && servicesResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
cache.put('/api/portfolio?featured=true', portfolioResponse.clone());
cache.put('/api/services?featured=true', servicesResponse.clone());
console.log('Content synced successfully');
}
} catch (error) {
console.error('Content sync failed:', error);
}
}
// Cache management utilities
async function cleanupCaches() {
const cacheNames = await caches.keys();
const currentCaches = [STATIC_CACHE_NAME, DYNAMIC_CACHE_NAME];
return Promise.all(
cacheNames.map(cacheName => {
if (!currentCaches.includes(cacheName)) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}
// Limit cache size
async function limitCacheSize(cacheName, maxItems) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxItems) {
const keysToDelete = keys.slice(0, keys.length - maxItems);
return Promise.all(keysToDelete.map(key => cache.delete(key)));
}
}
// Performance monitoring
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
const start = performance.now();
event.respondWith(
fetch(event.request).then(response => {
const duration = performance.now() - start;
// Log slow API requests
if (duration > 2000) {
console.warn('Slow API request:', event.request.url, duration + 'ms');
}
return response;
})
);
}
});
// Error tracking
self.addEventListener('error', event => {
console.error('Service Worker error:', event.error);
// Could send to analytics service
});
self.addEventListener('unhandledrejection', event => {
console.error('Service Worker unhandled rejection:', event.reason);
// Could send to analytics service
});

View File

@@ -0,0 +1,387 @@
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const User = require('../models/User');
const Portfolio = require('../models/Portfolio');
const Service = require('../models/Service');
const Contact = require('../models/Contact');
const SiteSettings = require('../models/SiteSettings');
// Authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.user) {
return res.redirect('/admin/login');
}
next();
};
// Admin login page
router.get('/login', (req, res) => {
if (req.session.user) {
return res.redirect('/admin/dashboard');
}
res.render('admin/login', {
title: 'Admin Login - SmartSolTech',
layout: 'admin/layout',
error: null
});
});
// Admin login POST
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email, isActive: true });
if (!user || !(await user.comparePassword(password))) {
return res.render('admin/login', {
title: 'Admin Login - SmartSolTech',
layout: 'admin/layout',
error: 'Invalid email or password'
});
}
await user.updateLastLogin();
req.session.user = {
id: user._id,
email: user.email,
name: user.name,
role: user.role
};
res.redirect('/admin/dashboard');
} catch (error) {
console.error('Admin login error:', error);
res.render('admin/login', {
title: 'Admin Login - SmartSolTech',
layout: 'admin/layout',
error: 'An error occurred. Please try again.'
});
}
});
// Admin logout
router.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
console.error('Logout error:', err);
}
res.redirect('/admin/login');
});
});
// Dashboard
router.get('/dashboard', requireAuth, async (req, res) => {
try {
const [
portfolioCount,
servicesCount,
contactsCount,
recentContacts,
recentPortfolio
] = await Promise.all([
Portfolio.countDocuments({ isPublished: true }),
Service.countDocuments({ isActive: true }),
Contact.countDocuments(),
Contact.find().sort({ createdAt: -1 }).limit(5),
Portfolio.find({ isPublished: true }).sort({ createdAt: -1 }).limit(5)
]);
const stats = {
portfolio: portfolioCount,
services: servicesCount,
contacts: contactsCount,
unreadContacts: await Contact.countDocuments({ isRead: false })
};
res.render('admin/dashboard', {
title: 'Dashboard - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
stats,
recentContacts,
recentPortfolio
});
} catch (error) {
console.error('Dashboard error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading dashboard'
});
}
});
// Portfolio management
router.get('/portfolio', requireAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 20;
const skip = (page - 1) * limit;
const [portfolio, total] = await Promise.all([
Portfolio.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Portfolio.countDocuments()
]);
const totalPages = Math.ceil(total / limit);
res.render('admin/portfolio/list', {
title: 'Portfolio Management - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
portfolio,
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Portfolio list error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading portfolio'
});
}
});
// Add portfolio item
router.get('/portfolio/add', requireAuth, (req, res) => {
res.render('admin/portfolio/add', {
title: 'Add Portfolio Item - Admin Panel',
layout: 'admin/layout',
user: req.session.user
});
});
// Edit portfolio item
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio) {
return res.status(404).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Portfolio item not found'
});
}
res.render('admin/portfolio/edit', {
title: 'Edit Portfolio Item - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
portfolio
});
} catch (error) {
console.error('Portfolio edit error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading portfolio item'
});
}
});
// Services management
router.get('/services', requireAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 20;
const skip = (page - 1) * limit;
const [services, total] = await Promise.all([
Service.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Service.countDocuments()
]);
const totalPages = Math.ceil(total / limit);
res.render('admin/services/list', {
title: 'Services Management - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
services,
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Services list error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading services'
});
}
});
// Add service
router.get('/services/add', requireAuth, (req, res) => {
res.render('admin/services/add', {
title: 'Add Service - Admin Panel',
layout: 'admin/layout',
user: req.session.user
});
});
// Edit service
router.get('/services/edit/:id', requireAuth, async (req, res) => {
try {
const service = await Service.findById(req.params.id)
.populate('portfolio', 'title');
if (!service) {
return res.status(404).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Service not found'
});
}
const availablePortfolio = await Portfolio.find({ isPublished: true })
.select('title category');
res.render('admin/services/edit', {
title: 'Edit Service - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
service,
availablePortfolio
});
} catch (error) {
console.error('Service edit error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading service'
});
}
});
// Contacts management
router.get('/contacts', requireAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 20;
const skip = (page - 1) * limit;
const status = req.query.status;
let query = {};
if (status && status !== 'all') {
query.status = status;
}
const [contacts, total] = await Promise.all([
Contact.find(query)
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Contact.countDocuments(query)
]);
const totalPages = Math.ceil(total / limit);
res.render('admin/contacts/list', {
title: 'Contacts Management - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
contacts,
currentStatus: status || 'all',
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Contacts list error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading contacts'
});
}
});
// View contact details
router.get('/contacts/:id', requireAuth, async (req, res) => {
try {
const contact = await Contact.findById(req.params.id);
if (!contact) {
return res.status(404).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Contact not found'
});
}
// Mark as read
if (!contact.isRead) {
contact.isRead = true;
await contact.save();
}
res.render('admin/contacts/view', {
title: 'Contact Details - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
contact
});
} catch (error) {
console.error('Contact view error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading contact'
});
}
});
// Settings
router.get('/settings', requireAuth, async (req, res) => {
try {
const settings = await SiteSettings.findOne() || new SiteSettings();
res.render('admin/settings', {
title: 'Site Settings - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
settings
});
} catch (error) {
console.error('Settings error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading settings'
});
}
});
// Media gallery
router.get('/media', requireAuth, (req, res) => {
res.render('admin/media', {
title: 'Media Gallery - Admin Panel',
layout: 'admin/layout',
user: req.session.user
});
});
module.exports = router;

View File

@@ -0,0 +1,387 @@
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const User = require('../models/User');
const Portfolio = require('../models/Portfolio');
const Service = require('../models/Service');
const Contact = require('../models/Contact');
const SiteSettings = require('../models/SiteSettings');
// Authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.user) {
return res.redirect('/admin/login');
}
next();
};
// Admin login page
router.get('/login', (req, res) => {
if (req.session.user) {
return res.redirect('/admin/dashboard');
}
res.render('admin/login', {
title: 'Admin Login - SmartSolTech',
layout: 'admin/layout',
error: null
});
});
// Admin login POST
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email, isActive: true });
if (!user || !(await user.comparePassword(password))) {
return res.render('admin/login', {
title: 'Admin Login - SmartSolTech',
layout: 'admin/layout',
error: 'Invalid email or password'
});
}
await user.updateLastLogin();
req.session.user = {
id: user._id,
email: user.email,
name: user.name,
role: user.role
};
res.redirect('/admin/dashboard');
} catch (error) {
console.error('Admin login error:', error);
res.render('admin/login', {
title: 'Admin Login - SmartSolTech',
layout: 'admin/layout',
error: 'An error occurred. Please try again.'
});
}
});
// Admin logout
router.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
console.error('Logout error:', err);
}
res.redirect('/admin/login');
});
});
// Dashboard
router.get('/dashboard', requireAuth, async (req, res) => {
try {
const [
portfolioCount,
servicesCount,
contactsCount,
recentContacts,
recentPortfolio
] = await Promise.all([
Portfolio.countDocuments({ isPublished: true }),
Service.countDocuments({ isActive: true }),
Contact.countDocuments(),
Contact.find().sort({ createdAt: -1 }).limit(5),
Portfolio.find({ isPublished: true }).sort({ createdAt: -1 }).limit(5)
]);
const stats = {
portfolio: portfolioCount,
services: servicesCount,
contacts: contactsCount,
unreadContacts: await Contact.countDocuments({ isRead: false })
};
res.render('admin/dashboard', {
title: 'Dashboard - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
stats,
recentContacts,
recentPortfolio
});
} catch (error) {
console.error('Dashboard error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading dashboard'
});
}
});
// Portfolio management
router.get('/portfolio', requireAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 20;
const skip = (page - 1) * limit;
const [portfolio, total] = await Promise.all([
Portfolio.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Portfolio.countDocuments()
]);
const totalPages = Math.ceil(total / limit);
res.render('admin/portfolio/list', {
title: 'Portfolio Management - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
portfolio,
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Portfolio list error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading portfolio'
});
}
});
// Add portfolio item
router.get('/portfolio/add', requireAuth, (req, res) => {
res.render('admin/portfolio/add', {
title: 'Add Portfolio Item - Admin Panel',
layout: 'admin/layout',
user: req.session.user
});
});
// Edit portfolio item
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio) {
return res.status(404).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Portfolio item not found'
});
}
res.render('admin/portfolio/edit', {
title: 'Edit Portfolio Item - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
portfolio
});
} catch (error) {
console.error('Portfolio edit error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading portfolio item'
});
}
});
// Services management
router.get('/services', requireAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 20;
const skip = (page - 1) * limit;
const [services, total] = await Promise.all([
Service.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Service.countDocuments()
]);
const totalPages = Math.ceil(total / limit);
res.render('admin/services/list', {
title: 'Services Management - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
services,
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Services list error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading services'
});
}
});
// Add service
router.get('/services/add', requireAuth, (req, res) => {
res.render('admin/services/add', {
title: 'Add Service - Admin Panel',
layout: 'admin/layout',
user: req.session.user
});
});
// Edit service
router.get('/services/edit/:id', requireAuth, async (req, res) => {
try {
const service = await Service.findById(req.params.id)
.populate('portfolio', 'title');
if (!service) {
return res.status(404).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Service not found'
});
}
const availablePortfolio = await Portfolio.find({ isPublished: true })
.select('title category');
res.render('admin/services/edit', {
title: 'Edit Service - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
service,
availablePortfolio
});
} catch (error) {
console.error('Service edit error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading service'
});
}
});
// Contacts management
router.get('/contacts', requireAuth, async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 20;
const skip = (page - 1) * limit;
const status = req.query.status;
let query = {};
if (status && status !== 'all') {
query.status = status;
}
const [contacts, total] = await Promise.all([
Contact.find(query)
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Contact.countDocuments(query)
]);
const totalPages = Math.ceil(total / limit);
res.render('admin/contacts/list', {
title: 'Contacts Management - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
contacts,
currentStatus: status || 'all',
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Contacts list error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading contacts'
});
}
});
// View contact details
router.get('/contacts/:id', requireAuth, async (req, res) => {
try {
const contact = await Contact.findById(req.params.id);
if (!contact) {
return res.status(404).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Contact not found'
});
}
// Mark as read
if (!contact.isRead) {
contact.isRead = true;
await contact.save();
}
res.render('admin/contacts/view', {
title: 'Contact Details - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
contact
});
} catch (error) {
console.error('Contact view error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading contact'
});
}
});
// Settings
router.get('/settings', requireAuth, async (req, res) => {
try {
const settings = await SiteSettings.findOne() || new SiteSettings();
res.render('admin/settings', {
title: 'Site Settings - Admin Panel',
layout: 'admin/layout',
user: req.session.user,
settings
});
} catch (error) {
console.error('Settings error:', error);
res.status(500).render('admin/error', {
title: 'Error - Admin Panel',
layout: 'admin/layout',
message: 'Error loading settings'
});
}
});
// Media gallery
router.get('/media', requireAuth, (req, res) => {
res.render('admin/media', {
title: 'Media Gallery - Admin Panel',
layout: 'admin/layout',
user: req.session.user
});
});
module.exports = router;

View File

@@ -0,0 +1,201 @@
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
const User = require('../models/User');
// Login validation rules
const loginValidation = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 6 })
];
// Login
router.post('/login', loginValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email, isActive: true });
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Check password
const isValidPassword = await user.comparePassword(password);
if (!isValidPassword) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Update last login
await user.updateLastLogin();
// Create JWT token
const token = jwt.sign(
{ userId: user._id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
// Set session
req.session.user = {
id: user._id,
email: user.email,
name: user.name,
role: user.role
};
res.json({
success: true,
message: 'Login successful',
token,
user: {
id: user._id,
email: user.email,
name: user.name,
role: user.role,
avatar: user.avatar
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
success: false,
message: 'Server error'
});
}
});
// Logout
router.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).json({
success: false,
message: 'Could not log out'
});
}
res.clearCookie('connect.sid');
res.json({
success: true,
message: 'Logout successful'
});
});
});
// Check authentication status
router.get('/me', async (req, res) => {
try {
if (!req.session.user) {
return res.status(401).json({
success: false,
message: 'Not authenticated'
});
}
const user = await User.findById(req.session.user.id)
.select('-password');
if (!user || !user.isActive) {
req.session.destroy();
return res.status(401).json({
success: false,
message: 'User not found or inactive'
});
}
res.json({
success: true,
user: {
id: user._id,
email: user.email,
name: user.name,
role: user.role,
avatar: user.avatar,
lastLogin: user.lastLogin
}
});
} catch (error) {
console.error('Auth check error:', error);
res.status(500).json({
success: false,
message: 'Server error'
});
}
});
// Change password
router.put('/change-password', [
body('currentPassword').isLength({ min: 6 }),
body('newPassword').isLength({ min: 6 })
], async (req, res) => {
try {
if (!req.session.user) {
return res.status(401).json({
success: false,
message: 'Not authenticated'
});
}
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const { currentPassword, newPassword } = req.body;
const user = await User.findById(req.session.user.id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
// Verify current password
const isValidPassword = await user.comparePassword(currentPassword);
if (!isValidPassword) {
return res.status(400).json({
success: false,
message: 'Current password is incorrect'
});
}
// Update password
user.password = newPassword;
await user.save();
res.json({
success: true,
message: 'Password updated successfully'
});
} catch (error) {
console.error('Change password error:', error);
res.status(500).json({
success: false,
message: 'Server error'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,201 @@
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
const User = require('../models/User');
// Login validation rules
const loginValidation = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 6 })
];
// Login
router.post('/login', loginValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email, isActive: true });
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Check password
const isValidPassword = await user.comparePassword(password);
if (!isValidPassword) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Update last login
await user.updateLastLogin();
// Create JWT token
const token = jwt.sign(
{ userId: user._id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
// Set session
req.session.user = {
id: user._id,
email: user.email,
name: user.name,
role: user.role
};
res.json({
success: true,
message: 'Login successful',
token,
user: {
id: user._id,
email: user.email,
name: user.name,
role: user.role,
avatar: user.avatar
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
success: false,
message: 'Server error'
});
}
});
// Logout
router.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).json({
success: false,
message: 'Could not log out'
});
}
res.clearCookie('connect.sid');
res.json({
success: true,
message: 'Logout successful'
});
});
});
// Check authentication status
router.get('/me', async (req, res) => {
try {
if (!req.session.user) {
return res.status(401).json({
success: false,
message: 'Not authenticated'
});
}
const user = await User.findById(req.session.user.id)
.select('-password');
if (!user || !user.isActive) {
req.session.destroy();
return res.status(401).json({
success: false,
message: 'User not found or inactive'
});
}
res.json({
success: true,
user: {
id: user._id,
email: user.email,
name: user.name,
role: user.role,
avatar: user.avatar,
lastLogin: user.lastLogin
}
});
} catch (error) {
console.error('Auth check error:', error);
res.status(500).json({
success: false,
message: 'Server error'
});
}
});
// Change password
router.put('/change-password', [
body('currentPassword').isLength({ min: 6 }),
body('newPassword').isLength({ min: 6 })
], async (req, res) => {
try {
if (!req.session.user) {
return res.status(401).json({
success: false,
message: 'Not authenticated'
});
}
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const { currentPassword, newPassword } = req.body;
const user = await User.findById(req.session.user.id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
// Verify current password
const isValidPassword = await user.comparePassword(currentPassword);
if (!isValidPassword) {
return res.status(400).json({
success: false,
message: 'Current password is incorrect'
});
}
// Update password
user.password = newPassword;
await user.save();
res.json({
success: true,
message: 'Password updated successfully'
});
} catch (error) {
console.error('Change password error:', error);
res.status(500).json({
success: false,
message: 'Server error'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,312 @@
const express = require('express');
const router = express.Router();
const Service = require('../models/Service');
// Get all services for calculator
router.get('/services', async (req, res) => {
try {
const services = await Service.find({ isActive: true })
.select('name pricing category features estimatedTime')
.sort({ category: 1, name: 1 });
const servicesByCategory = services.reduce((acc, service) => {
if (!acc[service.category]) {
acc[service.category] = [];
}
acc[service.category].push(service);
return acc;
}, {});
res.json({
success: true,
services: servicesByCategory,
allServices: services
});
} catch (error) {
console.error('Calculator services error:', error);
res.status(500).json({
success: false,
message: 'Error fetching services'
});
}
});
// Calculate project estimate
router.post('/calculate', async (req, res) => {
try {
const {
selectedServices = [],
projectComplexity = 'medium',
timeline = 'standard',
additionalFeatures = [],
customRequirements = ''
} = req.body;
if (!selectedServices.length) {
return res.status(400).json({
success: false,
message: 'Please select at least one service'
});
}
// Get selected services details
const services = await Service.find({
_id: { $in: selectedServices },
isActive: true
});
if (services.length !== selectedServices.length) {
return res.status(400).json({
success: false,
message: 'Some selected services are not available'
});
}
// Calculate base cost
let baseCost = 0;
let totalTime = 0; // in days
services.forEach(service => {
baseCost += service.pricing.basePrice;
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
});
// Apply complexity multiplier
const complexityMultipliers = {
'simple': 0.7,
'medium': 1.0,
'complex': 1.5,
'enterprise': 2.0
};
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
// Apply timeline multiplier
const timelineMultipliers = {
'rush': 1.8, // Less than 2 weeks
'fast': 1.4, // 2-4 weeks
'standard': 1.0, // 1-3 months
'flexible': 0.8 // 3+ months
};
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
// Additional features cost
const featureCosts = {
'seo-optimization': 300000,
'analytics-setup': 150000,
'social-integration': 200000,
'payment-gateway': 500000,
'multilingual': 400000,
'admin-panel': 600000,
'api-integration': 350000,
'mobile-responsive': 250000,
'ssl-certificate': 100000,
'backup-system': 200000
};
let additionalCost = 0;
additionalFeatures.forEach(feature => {
additionalCost += featureCosts[feature] || 0;
});
// Custom requirements cost (estimated based on description length and complexity)
let customCost = 0;
if (customRequirements && customRequirements.length > 50) {
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
}
// Calculate final estimate
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
const total = subtotal + additionalCost + customCost;
// Calculate time estimate
const timeMultiplier = complexityMultiplier * timelineMultiplier;
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
const estimatedWeeks = Math.ceil(estimatedDays / 7);
// Create price ranges (±20%)
const minPrice = Math.round(total * 0.8);
const maxPrice = Math.round(total * 1.2);
// Breakdown
const breakdown = {
baseServices: Math.round(baseCost),
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
additionalFeatures: additionalCost,
customRequirements: customCost,
subtotal: Math.round(subtotal),
total: Math.round(total)
};
const result = {
success: true,
estimate: {
total: Math.round(total),
range: {
min: minPrice,
max: maxPrice,
formatted: `${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
},
breakdown,
timeline: {
days: estimatedDays,
weeks: estimatedWeeks,
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
},
currency: 'KRW',
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
recommendations: generateRecommendations(projectComplexity, timeline, services)
},
selectedServices: services.map(s => ({
id: s._id,
name: s.name,
category: s.category,
basePrice: s.pricing.basePrice
})),
calculatedAt: new Date()
};
res.json(result);
} catch (error) {
console.error('Calculator calculation error:', error);
res.status(500).json({
success: false,
message: 'Error calculating estimate'
});
}
});
// Helper function to calculate confidence level
function calculateConfidence(serviceCount, complexity, customRequirements) {
let confidence = 85; // Base confidence
// Adjust based on service count
if (serviceCount === 1) confidence += 10;
else if (serviceCount > 5) confidence -= 10;
// Adjust based on complexity
if (complexity === 'simple') confidence += 10;
else if (complexity === 'enterprise') confidence -= 15;
// Adjust based on custom requirements
if (customRequirements && customRequirements.length > 200) {
confidence -= 20;
}
return Math.max(60, Math.min(95, confidence));
}
// Helper function to generate recommendations
function generateRecommendations(complexity, timeline, services) {
const recommendations = [];
if (complexity === 'enterprise' && timeline === 'rush') {
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
}
if (services.length > 6) {
recommendations.push('Consider breaking down into phases for better project management');
}
if (timeline === 'flexible') {
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
}
const hasDesign = services.some(s => s.category === 'design');
const hasDevelopment = services.some(s => s.category === 'development');
if (hasDevelopment && !hasDesign) {
recommendations.push('Consider adding UI/UX design services for better user experience');
}
return recommendations;
}
// Get pricing guidelines
router.get('/pricing-guide', async (req, res) => {
try {
const pricingGuide = {
serviceCategories: {
'development': {
name: 'Web Development',
priceRange: '₩500,000 - ₩5,000,000',
description: 'Custom web applications and websites'
},
'design': {
name: 'UI/UX Design',
priceRange: '₩300,000 - ₩2,000,000',
description: 'User interface and experience design'
},
'marketing': {
name: 'Digital Marketing',
priceRange: '₩200,000 - ₩1,500,000',
description: 'SEO, social media, and online marketing'
},
'consulting': {
name: 'Technical Consulting',
priceRange: '₩150,000 - ₩1,000,000',
description: 'Technology strategy and consultation'
}
},
complexityFactors: {
'simple': {
name: 'Simple Project',
multiplier: '0.7x',
description: 'Basic functionality, standard design'
},
'medium': {
name: 'Medium Project',
multiplier: '1.0x',
description: 'Moderate complexity, custom features'
},
'complex': {
name: 'Complex Project',
multiplier: '1.5x',
description: 'Advanced features, integrations'
},
'enterprise': {
name: 'Enterprise Project',
multiplier: '2.0x',
description: 'Large scale, high complexity'
}
},
timelineImpact: {
'rush': {
name: 'Rush (< 2 weeks)',
multiplier: '+80%',
description: 'Requires overtime and priority handling'
},
'fast': {
name: 'Fast (2-4 weeks)',
multiplier: '+40%',
description: 'Accelerated timeline'
},
'standard': {
name: 'Standard (1-3 months)',
multiplier: 'Standard',
description: 'Normal project timeline'
},
'flexible': {
name: 'Flexible (3+ months)',
multiplier: '-20%',
description: 'Extended timeline allows optimization'
}
}
};
res.json({
success: true,
pricingGuide
});
} catch (error) {
console.error('Pricing guide error:', error);
res.status(500).json({
success: false,
message: 'Error fetching pricing guide'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,312 @@
const express = require('express');
const router = express.Router();
const Service = require('../models/Service');
// Get all services for calculator
router.get('/services', async (req, res) => {
try {
const services = await Service.find({ isActive: true })
.select('name pricing category features estimatedTime')
.sort({ category: 1, name: 1 });
const servicesByCategory = services.reduce((acc, service) => {
if (!acc[service.category]) {
acc[service.category] = [];
}
acc[service.category].push(service);
return acc;
}, {});
res.json({
success: true,
services: servicesByCategory,
allServices: services
});
} catch (error) {
console.error('Calculator services error:', error);
res.status(500).json({
success: false,
message: 'Error fetching services'
});
}
});
// Calculate project estimate
router.post('/calculate', async (req, res) => {
try {
const {
selectedServices = [],
projectComplexity = 'medium',
timeline = 'standard',
additionalFeatures = [],
customRequirements = ''
} = req.body;
if (!selectedServices.length) {
return res.status(400).json({
success: false,
message: 'Please select at least one service'
});
}
// Get selected services details
const services = await Service.find({
_id: { $in: selectedServices },
isActive: true
});
if (services.length !== selectedServices.length) {
return res.status(400).json({
success: false,
message: 'Some selected services are not available'
});
}
// Calculate base cost
let baseCost = 0;
let totalTime = 0; // in days
services.forEach(service => {
baseCost += service.pricing.basePrice;
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
});
// Apply complexity multiplier
const complexityMultipliers = {
'simple': 0.7,
'medium': 1.0,
'complex': 1.5,
'enterprise': 2.0
};
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
// Apply timeline multiplier
const timelineMultipliers = {
'rush': 1.8, // Less than 2 weeks
'fast': 1.4, // 2-4 weeks
'standard': 1.0, // 1-3 months
'flexible': 0.8 // 3+ months
};
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
// Additional features cost
const featureCosts = {
'seo-optimization': 300000,
'analytics-setup': 150000,
'social-integration': 200000,
'payment-gateway': 500000,
'multilingual': 400000,
'admin-panel': 600000,
'api-integration': 350000,
'mobile-responsive': 250000,
'ssl-certificate': 100000,
'backup-system': 200000
};
let additionalCost = 0;
additionalFeatures.forEach(feature => {
additionalCost += featureCosts[feature] || 0;
});
// Custom requirements cost (estimated based on description length and complexity)
let customCost = 0;
if (customRequirements && customRequirements.length > 50) {
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
}
// Calculate final estimate
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
const total = subtotal + additionalCost + customCost;
// Calculate time estimate
const timeMultiplier = complexityMultiplier * timelineMultiplier;
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
const estimatedWeeks = Math.ceil(estimatedDays / 7);
// Create price ranges (±20%)
const minPrice = Math.round(total * 0.8);
const maxPrice = Math.round(total * 1.2);
// Breakdown
const breakdown = {
baseServices: Math.round(baseCost),
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
additionalFeatures: additionalCost,
customRequirements: customCost,
subtotal: Math.round(subtotal),
total: Math.round(total)
};
const result = {
success: true,
estimate: {
total: Math.round(total),
range: {
min: minPrice,
max: maxPrice,
formatted: `${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
},
breakdown,
timeline: {
days: estimatedDays,
weeks: estimatedWeeks,
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
},
currency: 'KRW',
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
recommendations: generateRecommendations(projectComplexity, timeline, services)
},
selectedServices: services.map(s => ({
id: s._id,
name: s.name,
category: s.category,
basePrice: s.pricing.basePrice
})),
calculatedAt: new Date()
};
res.json(result);
} catch (error) {
console.error('Calculator calculation error:', error);
res.status(500).json({
success: false,
message: 'Error calculating estimate'
});
}
});
// Helper function to calculate confidence level
function calculateConfidence(serviceCount, complexity, customRequirements) {
let confidence = 85; // Base confidence
// Adjust based on service count
if (serviceCount === 1) confidence += 10;
else if (serviceCount > 5) confidence -= 10;
// Adjust based on complexity
if (complexity === 'simple') confidence += 10;
else if (complexity === 'enterprise') confidence -= 15;
// Adjust based on custom requirements
if (customRequirements && customRequirements.length > 200) {
confidence -= 20;
}
return Math.max(60, Math.min(95, confidence));
}
// Helper function to generate recommendations
function generateRecommendations(complexity, timeline, services) {
const recommendations = [];
if (complexity === 'enterprise' && timeline === 'rush') {
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
}
if (services.length > 6) {
recommendations.push('Consider breaking down into phases for better project management');
}
if (timeline === 'flexible') {
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
}
const hasDesign = services.some(s => s.category === 'design');
const hasDevelopment = services.some(s => s.category === 'development');
if (hasDevelopment && !hasDesign) {
recommendations.push('Consider adding UI/UX design services for better user experience');
}
return recommendations;
}
// Get pricing guidelines
router.get('/pricing-guide', async (req, res) => {
try {
const pricingGuide = {
serviceCategories: {
'development': {
name: 'Web Development',
priceRange: '₩500,000 - ₩5,000,000',
description: 'Custom web applications and websites'
},
'design': {
name: 'UI/UX Design',
priceRange: '₩300,000 - ₩2,000,000',
description: 'User interface and experience design'
},
'marketing': {
name: 'Digital Marketing',
priceRange: '₩200,000 - ₩1,500,000',
description: 'SEO, social media, and online marketing'
},
'consulting': {
name: 'Technical Consulting',
priceRange: '₩150,000 - ₩1,000,000',
description: 'Technology strategy and consultation'
}
},
complexityFactors: {
'simple': {
name: 'Simple Project',
multiplier: '0.7x',
description: 'Basic functionality, standard design'
},
'medium': {
name: 'Medium Project',
multiplier: '1.0x',
description: 'Moderate complexity, custom features'
},
'complex': {
name: 'Complex Project',
multiplier: '1.5x',
description: 'Advanced features, integrations'
},
'enterprise': {
name: 'Enterprise Project',
multiplier: '2.0x',
description: 'Large scale, high complexity'
}
},
timelineImpact: {
'rush': {
name: 'Rush (< 2 weeks)',
multiplier: '+80%',
description: 'Requires overtime and priority handling'
},
'fast': {
name: 'Fast (2-4 weeks)',
multiplier: '+40%',
description: 'Accelerated timeline'
},
'standard': {
name: 'Standard (1-3 months)',
multiplier: 'Standard',
description: 'Normal project timeline'
},
'flexible': {
name: 'Flexible (3+ months)',
multiplier: '-20%',
description: 'Extended timeline allows optimization'
}
}
};
res.json({
success: true,
pricingGuide
});
} catch (error) {
console.error('Pricing guide error:', error);
res.status(500).json({
success: false,
message: 'Error fetching pricing guide'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,250 @@
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const nodemailer = require('nodemailer');
const Contact = require('../models/Contact');
const TelegramBot = require('node-telegram-bot-api');
// Initialize Telegram bot if token is provided
let bot = null;
if (process.env.TELEGRAM_BOT_TOKEN) {
bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, { polling: false });
}
// Contact form validation
const contactValidation = [
body('name').trim().isLength({ min: 2, max: 100 }),
body('email').isEmail().normalizeEmail(),
body('subject').trim().isLength({ min: 5, max: 200 }),
body('message').trim().isLength({ min: 10, max: 2000 }),
body('phone').optional().isMobilePhone(),
body('company').optional().trim().isLength({ max: 100 })
];
// Submit contact form
router.post('/submit', contactValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const contactData = {
...req.body,
ipAddress: req.ip,
userAgent: req.get('User-Agent'),
source: 'website'
};
// Save to database
const contact = new Contact(contactData);
await contact.save();
// Send email notification
await sendEmailNotification(contact);
// Send Telegram notification
await sendTelegramNotification(contact);
res.json({
success: true,
message: 'Your message has been sent successfully. We will get back to you soon!',
contactId: contact._id
});
} catch (error) {
console.error('Contact form error:', error);
res.status(500).json({
success: false,
message: 'Sorry, there was an error sending your message. Please try again.'
});
}
});
// Get project estimate
router.post('/estimate', [
body('services').isArray().notEmpty(),
body('projectType').notEmpty(),
body('timeline').notEmpty(),
body('budget').notEmpty(),
body('description').trim().isLength({ min: 10, max: 1000 })
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
// Calculate estimate (simplified)
const estimate = calculateProjectEstimate(services, projectType, timeline);
// Save inquiry to database
const contactData = {
name: contactInfo.name,
email: contactInfo.email,
phone: contactInfo.phone,
company: contactInfo.company,
subject: `Project Estimate Request - ${projectType}`,
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
serviceInterest: projectType,
budget: budget,
timeline: timeline,
source: 'calculator',
ipAddress: req.ip,
userAgent: req.get('User-Agent')
};
const contact = new Contact(contactData);
await contact.save();
// Send notifications
await sendEmailNotification(contact);
await sendTelegramNotification(contact);
res.json({
success: true,
message: 'Your project estimate request has been submitted successfully!',
estimate: estimate,
contactId: contact._id
});
} catch (error) {
console.error('Estimate request error:', error);
res.status(500).json({
success: false,
message: 'Sorry, there was an error processing your request. Please try again.'
});
}
});
// Helper function to send email notification
async function sendEmailNotification(contact) {
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
console.log('Email configuration not provided, skipping email notification');
return;
}
try {
const transporter = nodemailer.createTransporter({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT || 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
const mailOptions = {
from: process.env.EMAIL_USER,
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
subject: `New Contact Form Submission: ${contact.subject}`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${contact.name}</p>
<p><strong>Email:</strong> ${contact.email}</p>
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
<p><strong>Subject:</strong> ${contact.subject}</p>
<p><strong>Message:</strong></p>
<p>${contact.message.replace(/\n/g, '<br>')}</p>
<p><strong>Source:</strong> ${contact.source}</p>
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
<hr>
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
`
};
await transporter.sendMail(mailOptions);
console.log('Email notification sent successfully');
} catch (error) {
console.error('Email notification error:', error);
}
}
// Helper function to send Telegram notification
async function sendTelegramNotification(contact) {
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
console.log('Telegram configuration not provided, skipping Telegram notification');
return;
}
try {
const message = `
🔔 *New Contact Form Submission*
👤 *Name:* ${contact.name}
📧 *Email:* ${contact.email}
📱 *Phone:* ${contact.phone || 'Not provided'}
🏢 *Company:* ${contact.company || 'Not provided'}
📝 *Subject:* ${contact.subject}
💬 *Message:*
${contact.message}
📍 *Source:* ${contact.source}
🕐 *Time:* ${contact.createdAt.toLocaleString()}
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
`;
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
parse_mode: 'Markdown',
disable_web_page_preview: true
});
console.log('Telegram notification sent successfully');
} catch (error) {
console.error('Telegram notification error:', error);
}
}
// Helper function to calculate project estimate
function calculateProjectEstimate(services, projectType, timeline) {
const baseRates = {
'web-development': 50000,
'mobile-app': 80000,
'ui-ux-design': 30000,
'branding': 20000,
'e-commerce': 70000,
'consulting': 40000
};
const timelineMultipliers = {
'asap': 1.5,
'1-month': 1.2,
'1-3-months': 1.0,
'3-6-months': 0.9,
'flexible': 0.8
};
let basePrice = baseRates[projectType] || 50000;
let multiplier = timelineMultipliers[timeline] || 1.0;
// Add service modifiers
let serviceModifier = 1.0;
if (services.length > 3) serviceModifier += 0.3;
if (services.length > 5) serviceModifier += 0.5;
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
const rangeMin = Math.round(totalEstimate * 0.8);
const rangeMax = Math.round(totalEstimate * 1.3);
return {
base: totalEstimate,
min: rangeMin,
max: rangeMax,
formatted: `${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
currency: 'KRW'
};
}
module.exports = router;

View File

@@ -0,0 +1,250 @@
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const nodemailer = require('nodemailer');
const Contact = require('../models/Contact');
const TelegramBot = require('node-telegram-bot-api');
// Initialize Telegram bot if token is provided
let bot = null;
if (process.env.TELEGRAM_BOT_TOKEN) {
bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, { polling: false });
}
// Contact form validation
const contactValidation = [
body('name').trim().isLength({ min: 2, max: 100 }),
body('email').isEmail().normalizeEmail(),
body('subject').trim().isLength({ min: 5, max: 200 }),
body('message').trim().isLength({ min: 10, max: 2000 }),
body('phone').optional().isMobilePhone(),
body('company').optional().trim().isLength({ max: 100 })
];
// Submit contact form
router.post('/submit', contactValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const contactData = {
...req.body,
ipAddress: req.ip,
userAgent: req.get('User-Agent'),
source: 'website'
};
// Save to database
const contact = new Contact(contactData);
await contact.save();
// Send email notification
await sendEmailNotification(contact);
// Send Telegram notification
await sendTelegramNotification(contact);
res.json({
success: true,
message: 'Your message has been sent successfully. We will get back to you soon!',
contactId: contact._id
});
} catch (error) {
console.error('Contact form error:', error);
res.status(500).json({
success: false,
message: 'Sorry, there was an error sending your message. Please try again.'
});
}
});
// Get project estimate
router.post('/estimate', [
body('services').isArray().notEmpty(),
body('projectType').notEmpty(),
body('timeline').notEmpty(),
body('budget').notEmpty(),
body('description').trim().isLength({ min: 10, max: 1000 })
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Invalid input data',
errors: errors.array()
});
}
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
// Calculate estimate (simplified)
const estimate = calculateProjectEstimate(services, projectType, timeline);
// Save inquiry to database
const contactData = {
name: contactInfo.name,
email: contactInfo.email,
phone: contactInfo.phone,
company: contactInfo.company,
subject: `Project Estimate Request - ${projectType}`,
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
serviceInterest: projectType,
budget: budget,
timeline: timeline,
source: 'calculator',
ipAddress: req.ip,
userAgent: req.get('User-Agent')
};
const contact = new Contact(contactData);
await contact.save();
// Send notifications
await sendEmailNotification(contact);
await sendTelegramNotification(contact);
res.json({
success: true,
message: 'Your project estimate request has been submitted successfully!',
estimate: estimate,
contactId: contact._id
});
} catch (error) {
console.error('Estimate request error:', error);
res.status(500).json({
success: false,
message: 'Sorry, there was an error processing your request. Please try again.'
});
}
});
// Helper function to send email notification
async function sendEmailNotification(contact) {
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
console.log('Email configuration not provided, skipping email notification');
return;
}
try {
const transporter = nodemailer.createTransporter({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT || 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
const mailOptions = {
from: process.env.EMAIL_USER,
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
subject: `New Contact Form Submission: ${contact.subject}`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${contact.name}</p>
<p><strong>Email:</strong> ${contact.email}</p>
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
<p><strong>Subject:</strong> ${contact.subject}</p>
<p><strong>Message:</strong></p>
<p>${contact.message.replace(/\n/g, '<br>')}</p>
<p><strong>Source:</strong> ${contact.source}</p>
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
<hr>
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
`
};
await transporter.sendMail(mailOptions);
console.log('Email notification sent successfully');
} catch (error) {
console.error('Email notification error:', error);
}
}
// Helper function to send Telegram notification
async function sendTelegramNotification(contact) {
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
console.log('Telegram configuration not provided, skipping Telegram notification');
return;
}
try {
const message = `
🔔 *New Contact Form Submission*
👤 *Name:* ${contact.name}
📧 *Email:* ${contact.email}
📱 *Phone:* ${contact.phone || 'Not provided'}
🏢 *Company:* ${contact.company || 'Not provided'}
📝 *Subject:* ${contact.subject}
💬 *Message:*
${contact.message}
📍 *Source:* ${contact.source}
🕐 *Time:* ${contact.createdAt.toLocaleString()}
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
`;
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
parse_mode: 'Markdown',
disable_web_page_preview: true
});
console.log('Telegram notification sent successfully');
} catch (error) {
console.error('Telegram notification error:', error);
}
}
// Helper function to calculate project estimate
function calculateProjectEstimate(services, projectType, timeline) {
const baseRates = {
'web-development': 50000,
'mobile-app': 80000,
'ui-ux-design': 30000,
'branding': 20000,
'e-commerce': 70000,
'consulting': 40000
};
const timelineMultipliers = {
'asap': 1.5,
'1-month': 1.2,
'1-3-months': 1.0,
'3-6-months': 0.9,
'flexible': 0.8
};
let basePrice = baseRates[projectType] || 50000;
let multiplier = timelineMultipliers[timeline] || 1.0;
// Add service modifiers
let serviceModifier = 1.0;
if (services.length > 3) serviceModifier += 0.3;
if (services.length > 5) serviceModifier += 0.5;
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
const rangeMin = Math.round(totalEstimate * 0.8);
const rangeMax = Math.round(totalEstimate * 1.3);
return {
base: totalEstimate,
min: rangeMin,
max: rangeMax,
formatted: `${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
currency: 'KRW'
};
}
module.exports = router;

View File

@@ -0,0 +1,203 @@
const express = require('express');
const router = express.Router();
const Portfolio = require('../models/Portfolio');
const Service = require('../models/Service');
const SiteSettings = require('../models/SiteSettings');
// Home page
router.get('/', async (req, res) => {
try {
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
SiteSettings.findOne() || {},
Portfolio.find({ featured: true, isPublished: true })
.sort({ order: 1, createdAt: -1 })
.limit(6),
Service.find({ featured: true, isActive: true })
.sort({ order: 1 })
.limit(4)
]);
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: settings || {},
featuredPortfolio,
featuredServices,
currentPage: 'home'
});
} catch (error) {
console.error('Home page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// About page
router.get('/about', async (req, res) => {
try {
const settings = await SiteSettings.findOne() || {};
res.render('about', {
title: 'About Us - SmartSolTech',
settings,
currentPage: 'about'
});
} catch (error) {
console.error('About page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Portfolio page
router.get('/portfolio', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 12;
const skip = (page - 1) * limit;
const category = req.query.category;
let query = { isPublished: true };
if (category && category !== 'all') {
query.category = category;
}
const [portfolio, total, categories] = await Promise.all([
Portfolio.find(query)
.sort({ featured: -1, publishedAt: -1 })
.skip(skip)
.limit(limit),
Portfolio.countDocuments(query),
Portfolio.distinct('category', { isPublished: true })
]);
const totalPages = Math.ceil(total / limit);
res.render('portfolio', {
title: 'Portfolio - SmartSolTech',
portfolio,
categories,
currentCategory: category || 'all',
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
},
currentPage: 'portfolio'
});
} catch (error) {
console.error('Portfolio page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Portfolio detail page
router.get('/portfolio/:id', async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio || !portfolio.isPublished) {
return res.status(404).render('404', {
title: '404 - Project Not Found',
message: 'The requested project was not found'
});
}
// Increment view count
portfolio.viewCount += 1;
await portfolio.save();
// Get related projects
const relatedProjects = await Portfolio.find({
_id: { $ne: portfolio._id },
category: portfolio.category,
isPublished: true
}).limit(3);
res.render('portfolio-detail', {
title: `${portfolio.title} - Portfolio - SmartSolTech`,
portfolio,
relatedProjects,
currentPage: 'portfolio'
});
} catch (error) {
console.error('Portfolio detail error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Services page
router.get('/services', async (req, res) => {
try {
const services = await Service.find({ isActive: true })
.sort({ featured: -1, order: 1 })
.populate('portfolio', 'title images');
const categories = await Service.distinct('category', { isActive: true });
res.render('services', {
title: 'Services - SmartSolTech',
services,
categories,
currentPage: 'services'
});
} catch (error) {
console.error('Services page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Calculator page
router.get('/calculator', async (req, res) => {
try {
const services = await Service.find({ isActive: true })
.select('name pricing category')
.sort({ category: 1, name: 1 });
res.render('calculator', {
title: 'Project Calculator - SmartSolTech',
services,
currentPage: 'calculator'
});
} catch (error) {
console.error('Calculator page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Contact page
router.get('/contact', async (req, res) => {
try {
const settings = await SiteSettings.findOne() || {};
res.render('contact', {
title: 'Contact Us - SmartSolTech',
settings,
currentPage: 'contact'
});
} catch (error) {
console.error('Contact page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,203 @@
const express = require('express');
const router = express.Router();
const Portfolio = require('../models/Portfolio');
const Service = require('../models/Service');
const SiteSettings = require('../models/SiteSettings');
// Home page
router.get('/', async (req, res) => {
try {
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
SiteSettings.findOne() || {},
Portfolio.find({ featured: true, isPublished: true })
.sort({ order: 1, createdAt: -1 })
.limit(6),
Service.find({ featured: true, isActive: true })
.sort({ order: 1 })
.limit(4)
]);
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: settings || {},
featuredPortfolio,
featuredServices,
currentPage: 'home'
});
} catch (error) {
console.error('Home page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// About page
router.get('/about', async (req, res) => {
try {
const settings = await SiteSettings.findOne() || {};
res.render('about', {
title: 'About Us - SmartSolTech',
settings,
currentPage: 'about'
});
} catch (error) {
console.error('About page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Portfolio page
router.get('/portfolio', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 12;
const skip = (page - 1) * limit;
const category = req.query.category;
let query = { isPublished: true };
if (category && category !== 'all') {
query.category = category;
}
const [portfolio, total, categories] = await Promise.all([
Portfolio.find(query)
.sort({ featured: -1, publishedAt: -1 })
.skip(skip)
.limit(limit),
Portfolio.countDocuments(query),
Portfolio.distinct('category', { isPublished: true })
]);
const totalPages = Math.ceil(total / limit);
res.render('portfolio', {
title: 'Portfolio - SmartSolTech',
portfolio,
categories,
currentCategory: category || 'all',
pagination: {
current: page,
total: totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
},
currentPage: 'portfolio'
});
} catch (error) {
console.error('Portfolio page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Portfolio detail page
router.get('/portfolio/:id', async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio || !portfolio.isPublished) {
return res.status(404).render('404', {
title: '404 - Project Not Found',
message: 'The requested project was not found'
});
}
// Increment view count
portfolio.viewCount += 1;
await portfolio.save();
// Get related projects
const relatedProjects = await Portfolio.find({
_id: { $ne: portfolio._id },
category: portfolio.category,
isPublished: true
}).limit(3);
res.render('portfolio-detail', {
title: `${portfolio.title} - Portfolio - SmartSolTech`,
portfolio,
relatedProjects,
currentPage: 'portfolio'
});
} catch (error) {
console.error('Portfolio detail error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Services page
router.get('/services', async (req, res) => {
try {
const services = await Service.find({ isActive: true })
.sort({ featured: -1, order: 1 })
.populate('portfolio', 'title images');
const categories = await Service.distinct('category', { isActive: true });
res.render('services', {
title: 'Services - SmartSolTech',
services,
categories,
currentPage: 'services'
});
} catch (error) {
console.error('Services page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Calculator page
router.get('/calculator', async (req, res) => {
try {
const services = await Service.find({ isActive: true })
.select('name pricing category')
.sort({ category: 1, name: 1 });
res.render('calculator', {
title: 'Project Calculator - SmartSolTech',
services,
currentPage: 'calculator'
});
} catch (error) {
console.error('Calculator page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
// Contact page
router.get('/contact', async (req, res) => {
try {
const settings = await SiteSettings.findOne() || {};
res.render('contact', {
title: 'Contact Us - SmartSolTech',
settings,
currentPage: 'contact'
});
} catch (error) {
console.error('Contact page error:', error);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,345 @@
const express = require('express');
const router = express.Router();
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs').promises;
// Authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
next();
};
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: async (req, file, cb) => {
const uploadPath = path.join(__dirname, '../public/uploads');
try {
await fs.mkdir(uploadPath, { recursive: true });
cb(null, uploadPath);
} catch (error) {
cb(error);
}
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
files: 10
},
fileFilter: (req, file, cb) => {
// Allow images only
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed'), false);
}
}
});
// Upload single image
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({
success: false,
message: 'No file uploaded'
});
}
const originalPath = req.file.path;
const filename = req.file.filename;
const nameWithoutExt = path.parse(filename).name;
// Create optimized versions
const sizes = {
thumbnail: { width: 300, height: 200 },
medium: { width: 800, height: 600 },
large: { width: 1200, height: 900 }
};
const optimizedImages = {};
for (const [sizeName, dimensions] of Object.entries(sizes)) {
const outputPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-${sizeName}.webp`
);
await sharp(originalPath)
.resize(dimensions.width, dimensions.height, {
fit: 'inside',
withoutEnlargement: true
})
.webp({ quality: 85 })
.toFile(outputPath);
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
}
// Keep original as well (converted to webp for better compression)
const originalWebpPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-original.webp`
);
await sharp(originalPath)
.webp({ quality: 90 })
.toFile(originalWebpPath);
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
// Remove the original uploaded file
await fs.unlink(originalPath);
res.json({
success: true,
message: 'Image uploaded and optimized successfully',
images: optimizedImages,
metadata: {
originalName: req.file.originalname,
mimeType: req.file.mimetype,
size: req.file.size
}
});
} catch (error) {
console.error('Image upload error:', error);
// Clean up files on error
if (req.file && req.file.path) {
try {
await fs.unlink(req.file.path);
} catch (unlinkError) {
console.error('Error removing file:', unlinkError);
}
}
res.status(500).json({
success: false,
message: 'Error uploading image'
});
}
});
// Upload multiple images
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
message: 'No files uploaded'
});
}
const uploadedImages = [];
for (const file of req.files) {
try {
const originalPath = file.path;
const filename = file.filename;
const nameWithoutExt = path.parse(filename).name;
// Create optimized versions
const sizes = {
thumbnail: { width: 300, height: 200 },
medium: { width: 800, height: 600 },
large: { width: 1200, height: 900 }
};
const optimizedImages = {};
for (const [sizeName, dimensions] of Object.entries(sizes)) {
const outputPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-${sizeName}.webp`
);
await sharp(originalPath)
.resize(dimensions.width, dimensions.height, {
fit: 'inside',
withoutEnlargement: true
})
.webp({ quality: 85 })
.toFile(outputPath);
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
}
// Original as webp
const originalWebpPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-original.webp`
);
await sharp(originalPath)
.webp({ quality: 90 })
.toFile(originalWebpPath);
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
uploadedImages.push({
originalName: file.originalname,
images: optimizedImages,
metadata: {
mimeType: file.mimetype,
size: file.size
}
});
// Remove original file
await fs.unlink(originalPath);
} catch (fileError) {
console.error(`Error processing file ${file.originalname}:`, fileError);
// Clean up this file and continue with others
try {
await fs.unlink(file.path);
} catch (unlinkError) {
console.error('Error removing file:', unlinkError);
}
}
}
res.json({
success: true,
message: `${uploadedImages.length} images uploaded and optimized successfully`,
images: uploadedImages
});
} catch (error) {
console.error('Multiple images upload error:', error);
// Clean up files on error
if (req.files) {
for (const file of req.files) {
try {
await fs.unlink(file.path);
} catch (unlinkError) {
console.error('Error removing file:', unlinkError);
}
}
}
res.status(500).json({
success: false,
message: 'Error uploading images'
});
}
});
// Delete image
router.delete('/:filename', requireAuth, async (req, res) => {
try {
const filename = req.params.filename;
const uploadPath = path.join(__dirname, '../public/uploads');
// Security check - ensure filename doesn't contain path traversal
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
return res.status(400).json({
success: false,
message: 'Invalid filename'
});
}
const filePath = path.join(uploadPath, filename);
try {
await fs.access(filePath);
await fs.unlink(filePath);
res.json({
success: true,
message: 'Image deleted successfully'
});
} catch (error) {
if (error.code === 'ENOENT') {
return res.status(404).json({
success: false,
message: 'Image not found'
});
}
throw error;
}
} catch (error) {
console.error('Image deletion error:', error);
res.status(500).json({
success: false,
message: 'Error deleting image'
});
}
});
// List uploaded images
router.get('/list', requireAuth, async (req, res) => {
try {
const uploadPath = path.join(__dirname, '../public/uploads');
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const files = await fs.readdir(uploadPath);
const imageFiles = files.filter(file =>
/\.(jpg|jpeg|png|gif|webp)$/i.test(file)
);
const total = imageFiles.length;
const totalPages = Math.ceil(total / limit);
const start = (page - 1) * limit;
const end = start + limit;
const paginatedFiles = imageFiles.slice(start, end);
const imagesWithStats = await Promise.all(
paginatedFiles.map(async (file) => {
try {
const filePath = path.join(uploadPath, file);
const stats = await fs.stat(filePath);
return {
filename: file,
url: `/uploads/${file}`,
size: stats.size,
modified: stats.mtime,
isImage: true
};
} catch (error) {
console.error(`Error getting stats for ${file}:`, error);
return null;
}
})
);
const validImages = imagesWithStats.filter(img => img !== null);
res.json({
success: true,
images: validImages,
pagination: {
current: page,
total: totalPages,
limit,
totalItems: total,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('List images error:', error);
res.status(500).json({
success: false,
message: 'Error listing images'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,345 @@
const express = require('express');
const router = express.Router();
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs').promises;
// Authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
next();
};
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: async (req, file, cb) => {
const uploadPath = path.join(__dirname, '../public/uploads');
try {
await fs.mkdir(uploadPath, { recursive: true });
cb(null, uploadPath);
} catch (error) {
cb(error);
}
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
files: 10
},
fileFilter: (req, file, cb) => {
// Allow images only
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed'), false);
}
}
});
// Upload single image
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({
success: false,
message: 'No file uploaded'
});
}
const originalPath = req.file.path;
const filename = req.file.filename;
const nameWithoutExt = path.parse(filename).name;
// Create optimized versions
const sizes = {
thumbnail: { width: 300, height: 200 },
medium: { width: 800, height: 600 },
large: { width: 1200, height: 900 }
};
const optimizedImages = {};
for (const [sizeName, dimensions] of Object.entries(sizes)) {
const outputPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-${sizeName}.webp`
);
await sharp(originalPath)
.resize(dimensions.width, dimensions.height, {
fit: 'inside',
withoutEnlargement: true
})
.webp({ quality: 85 })
.toFile(outputPath);
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
}
// Keep original as well (converted to webp for better compression)
const originalWebpPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-original.webp`
);
await sharp(originalPath)
.webp({ quality: 90 })
.toFile(originalWebpPath);
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
// Remove the original uploaded file
await fs.unlink(originalPath);
res.json({
success: true,
message: 'Image uploaded and optimized successfully',
images: optimizedImages,
metadata: {
originalName: req.file.originalname,
mimeType: req.file.mimetype,
size: req.file.size
}
});
} catch (error) {
console.error('Image upload error:', error);
// Clean up files on error
if (req.file && req.file.path) {
try {
await fs.unlink(req.file.path);
} catch (unlinkError) {
console.error('Error removing file:', unlinkError);
}
}
res.status(500).json({
success: false,
message: 'Error uploading image'
});
}
});
// Upload multiple images
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
message: 'No files uploaded'
});
}
const uploadedImages = [];
for (const file of req.files) {
try {
const originalPath = file.path;
const filename = file.filename;
const nameWithoutExt = path.parse(filename).name;
// Create optimized versions
const sizes = {
thumbnail: { width: 300, height: 200 },
medium: { width: 800, height: 600 },
large: { width: 1200, height: 900 }
};
const optimizedImages = {};
for (const [sizeName, dimensions] of Object.entries(sizes)) {
const outputPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-${sizeName}.webp`
);
await sharp(originalPath)
.resize(dimensions.width, dimensions.height, {
fit: 'inside',
withoutEnlargement: true
})
.webp({ quality: 85 })
.toFile(outputPath);
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
}
// Original as webp
const originalWebpPath = path.join(
path.dirname(originalPath),
`${nameWithoutExt}-original.webp`
);
await sharp(originalPath)
.webp({ quality: 90 })
.toFile(originalWebpPath);
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
uploadedImages.push({
originalName: file.originalname,
images: optimizedImages,
metadata: {
mimeType: file.mimetype,
size: file.size
}
});
// Remove original file
await fs.unlink(originalPath);
} catch (fileError) {
console.error(`Error processing file ${file.originalname}:`, fileError);
// Clean up this file and continue with others
try {
await fs.unlink(file.path);
} catch (unlinkError) {
console.error('Error removing file:', unlinkError);
}
}
}
res.json({
success: true,
message: `${uploadedImages.length} images uploaded and optimized successfully`,
images: uploadedImages
});
} catch (error) {
console.error('Multiple images upload error:', error);
// Clean up files on error
if (req.files) {
for (const file of req.files) {
try {
await fs.unlink(file.path);
} catch (unlinkError) {
console.error('Error removing file:', unlinkError);
}
}
}
res.status(500).json({
success: false,
message: 'Error uploading images'
});
}
});
// Delete image
router.delete('/:filename', requireAuth, async (req, res) => {
try {
const filename = req.params.filename;
const uploadPath = path.join(__dirname, '../public/uploads');
// Security check - ensure filename doesn't contain path traversal
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
return res.status(400).json({
success: false,
message: 'Invalid filename'
});
}
const filePath = path.join(uploadPath, filename);
try {
await fs.access(filePath);
await fs.unlink(filePath);
res.json({
success: true,
message: 'Image deleted successfully'
});
} catch (error) {
if (error.code === 'ENOENT') {
return res.status(404).json({
success: false,
message: 'Image not found'
});
}
throw error;
}
} catch (error) {
console.error('Image deletion error:', error);
res.status(500).json({
success: false,
message: 'Error deleting image'
});
}
});
// List uploaded images
router.get('/list', requireAuth, async (req, res) => {
try {
const uploadPath = path.join(__dirname, '../public/uploads');
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const files = await fs.readdir(uploadPath);
const imageFiles = files.filter(file =>
/\.(jpg|jpeg|png|gif|webp)$/i.test(file)
);
const total = imageFiles.length;
const totalPages = Math.ceil(total / limit);
const start = (page - 1) * limit;
const end = start + limit;
const paginatedFiles = imageFiles.slice(start, end);
const imagesWithStats = await Promise.all(
paginatedFiles.map(async (file) => {
try {
const filePath = path.join(uploadPath, file);
const stats = await fs.stat(filePath);
return {
filename: file,
url: `/uploads/${file}`,
size: stats.size,
modified: stats.mtime,
isImage: true
};
} catch (error) {
console.error(`Error getting stats for ${file}:`, error);
return null;
}
})
);
const validImages = imagesWithStats.filter(img => img !== null);
res.json({
success: true,
images: validImages,
pagination: {
current: page,
total: totalPages,
limit,
totalItems: total,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('List images error:', error);
res.status(500).json({
success: false,
message: 'Error listing images'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,196 @@
const express = require('express');
const router = express.Router();
const Portfolio = require('../models/Portfolio');
// Get all portfolio items
router.get('/', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 12;
const skip = (page - 1) * limit;
const category = req.query.category;
const search = req.query.search;
const featured = req.query.featured;
// Build query
let query = { isPublished: true };
if (category && category !== 'all') {
query.category = category;
}
if (featured === 'true') {
query.featured = true;
}
if (search) {
query.$text = { $search: search };
}
// Get portfolio items
const [portfolio, total] = await Promise.all([
Portfolio.find(query)
.sort({ featured: -1, publishedAt: -1 })
.skip(skip)
.limit(limit)
.select('title shortDescription category technologies images status publishedAt viewCount'),
Portfolio.countDocuments(query)
]);
const totalPages = Math.ceil(total / limit);
res.json({
success: true,
portfolio,
pagination: {
current: page,
total: totalPages,
limit,
totalItems: total,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Portfolio API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching portfolio'
});
}
});
// Get single portfolio item
router.get('/:id', async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio || !portfolio.isPublished) {
return res.status(404).json({
success: false,
message: 'Portfolio item not found'
});
}
// Increment view count
portfolio.viewCount += 1;
await portfolio.save();
// Get related projects
const relatedProjects = await Portfolio.find({
_id: { $ne: portfolio._id },
category: portfolio.category,
isPublished: true
})
.select('title shortDescription images')
.limit(4);
res.json({
success: true,
portfolio,
relatedProjects
});
} catch (error) {
console.error('Portfolio detail API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching portfolio item'
});
}
});
// Get portfolio categories
router.get('/meta/categories', async (req, res) => {
try {
const categories = await Portfolio.distinct('category', { isPublished: true });
// Get count for each category
const categoriesWithCount = await Promise.all(
categories.map(async (category) => {
const count = await Portfolio.countDocuments({
category,
isPublished: true
});
return { category, count };
})
);
res.json({
success: true,
categories: categoriesWithCount
});
} catch (error) {
console.error('Portfolio categories API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching categories'
});
}
});
// Like portfolio item
router.post('/:id/like', async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio || !portfolio.isPublished) {
return res.status(404).json({
success: false,
message: 'Portfolio item not found'
});
}
portfolio.likes += 1;
await portfolio.save();
res.json({
success: true,
likes: portfolio.likes
});
} catch (error) {
console.error('Portfolio like API error:', error);
res.status(500).json({
success: false,
message: 'Error liking portfolio item'
});
}
});
// Search portfolio
router.get('/search/:term', async (req, res) => {
try {
const searchTerm = req.params.term;
const limit = parseInt(req.query.limit) || 10;
const portfolio = await Portfolio.find({
$and: [
{ isPublished: true },
{
$or: [
{ title: { $regex: searchTerm, $options: 'i' } },
{ description: { $regex: searchTerm, $options: 'i' } },
{ technologies: { $in: [new RegExp(searchTerm, 'i')] } }
]
}
]
})
.select('title shortDescription category images')
.sort({ featured: -1, publishedAt: -1 })
.limit(limit);
res.json({
success: true,
portfolio,
searchTerm,
count: portfolio.length
});
} catch (error) {
console.error('Portfolio search API error:', error);
res.status(500).json({
success: false,
message: 'Error searching portfolio'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,196 @@
const express = require('express');
const router = express.Router();
const Portfolio = require('../models/Portfolio');
// Get all portfolio items
router.get('/', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 12;
const skip = (page - 1) * limit;
const category = req.query.category;
const search = req.query.search;
const featured = req.query.featured;
// Build query
let query = { isPublished: true };
if (category && category !== 'all') {
query.category = category;
}
if (featured === 'true') {
query.featured = true;
}
if (search) {
query.$text = { $search: search };
}
// Get portfolio items
const [portfolio, total] = await Promise.all([
Portfolio.find(query)
.sort({ featured: -1, publishedAt: -1 })
.skip(skip)
.limit(limit)
.select('title shortDescription category technologies images status publishedAt viewCount'),
Portfolio.countDocuments(query)
]);
const totalPages = Math.ceil(total / limit);
res.json({
success: true,
portfolio,
pagination: {
current: page,
total: totalPages,
limit,
totalItems: total,
hasNext: page < totalPages,
hasPrev: page > 1
}
});
} catch (error) {
console.error('Portfolio API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching portfolio'
});
}
});
// Get single portfolio item
router.get('/:id', async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio || !portfolio.isPublished) {
return res.status(404).json({
success: false,
message: 'Portfolio item not found'
});
}
// Increment view count
portfolio.viewCount += 1;
await portfolio.save();
// Get related projects
const relatedProjects = await Portfolio.find({
_id: { $ne: portfolio._id },
category: portfolio.category,
isPublished: true
})
.select('title shortDescription images')
.limit(4);
res.json({
success: true,
portfolio,
relatedProjects
});
} catch (error) {
console.error('Portfolio detail API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching portfolio item'
});
}
});
// Get portfolio categories
router.get('/meta/categories', async (req, res) => {
try {
const categories = await Portfolio.distinct('category', { isPublished: true });
// Get count for each category
const categoriesWithCount = await Promise.all(
categories.map(async (category) => {
const count = await Portfolio.countDocuments({
category,
isPublished: true
});
return { category, count };
})
);
res.json({
success: true,
categories: categoriesWithCount
});
} catch (error) {
console.error('Portfolio categories API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching categories'
});
}
});
// Like portfolio item
router.post('/:id/like', async (req, res) => {
try {
const portfolio = await Portfolio.findById(req.params.id);
if (!portfolio || !portfolio.isPublished) {
return res.status(404).json({
success: false,
message: 'Portfolio item not found'
});
}
portfolio.likes += 1;
await portfolio.save();
res.json({
success: true,
likes: portfolio.likes
});
} catch (error) {
console.error('Portfolio like API error:', error);
res.status(500).json({
success: false,
message: 'Error liking portfolio item'
});
}
});
// Search portfolio
router.get('/search/:term', async (req, res) => {
try {
const searchTerm = req.params.term;
const limit = parseInt(req.query.limit) || 10;
const portfolio = await Portfolio.find({
$and: [
{ isPublished: true },
{
$or: [
{ title: { $regex: searchTerm, $options: 'i' } },
{ description: { $regex: searchTerm, $options: 'i' } },
{ technologies: { $in: [new RegExp(searchTerm, 'i')] } }
]
}
]
})
.select('title shortDescription category images')
.sort({ featured: -1, publishedAt: -1 })
.limit(limit);
res.json({
success: true,
portfolio,
searchTerm,
count: portfolio.length
});
} catch (error) {
console.error('Portfolio search API error:', error);
res.status(500).json({
success: false,
message: 'Error searching portfolio'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,141 @@
const express = require('express');
const router = express.Router();
const Service = require('../models/Service');
const Portfolio = require('../models/Portfolio');
// Get all services
router.get('/', async (req, res) => {
try {
const category = req.query.category;
const featured = req.query.featured;
let query = { isActive: true };
if (category && category !== 'all') {
query.category = category;
}
if (featured === 'true') {
query.featured = true;
}
const services = await Service.find(query)
.populate('portfolio', 'title images')
.sort({ featured: -1, order: 1 });
res.json({
success: true,
services
});
} catch (error) {
console.error('Services API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching services'
});
}
});
// Get single service
router.get('/:id', async (req, res) => {
try {
const service = await Service.findById(req.params.id)
.populate('portfolio', 'title shortDescription images category');
if (!service || !service.isActive) {
return res.status(404).json({
success: false,
message: 'Service not found'
});
}
// Get related services
const relatedServices = await Service.find({
_id: { $ne: service._id },
category: service.category,
isActive: true
})
.select('name shortDescription icon pricing')
.limit(3);
res.json({
success: true,
service,
relatedServices
});
} catch (error) {
console.error('Service detail API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching service'
});
}
});
// Get service categories
router.get('/meta/categories', async (req, res) => {
try {
const categories = await Service.distinct('category', { isActive: true });
// Get count for each category
const categoriesWithCount = await Promise.all(
categories.map(async (category) => {
const count = await Service.countDocuments({
category,
isActive: true
});
return { category, count };
})
);
res.json({
success: true,
categories: categoriesWithCount
});
} catch (error) {
console.error('Service categories API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching categories'
});
}
});
// Search services
router.get('/search/:term', async (req, res) => {
try {
const searchTerm = req.params.term;
const limit = parseInt(req.query.limit) || 10;
const services = await Service.find({
$and: [
{ isActive: true },
{
$or: [
{ name: { $regex: searchTerm, $options: 'i' } },
{ description: { $regex: searchTerm, $options: 'i' } },
{ tags: { $in: [new RegExp(searchTerm, 'i')] } }
]
}
]
})
.select('name shortDescription icon pricing category')
.sort({ featured: -1, order: 1 })
.limit(limit);
res.json({
success: true,
services,
searchTerm,
count: services.length
});
} catch (error) {
console.error('Service search API error:', error);
res.status(500).json({
success: false,
message: 'Error searching services'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,141 @@
const express = require('express');
const router = express.Router();
const Service = require('../models/Service');
const Portfolio = require('../models/Portfolio');
// Get all services
router.get('/', async (req, res) => {
try {
const category = req.query.category;
const featured = req.query.featured;
let query = { isActive: true };
if (category && category !== 'all') {
query.category = category;
}
if (featured === 'true') {
query.featured = true;
}
const services = await Service.find(query)
.populate('portfolio', 'title images')
.sort({ featured: -1, order: 1 });
res.json({
success: true,
services
});
} catch (error) {
console.error('Services API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching services'
});
}
});
// Get single service
router.get('/:id', async (req, res) => {
try {
const service = await Service.findById(req.params.id)
.populate('portfolio', 'title shortDescription images category');
if (!service || !service.isActive) {
return res.status(404).json({
success: false,
message: 'Service not found'
});
}
// Get related services
const relatedServices = await Service.find({
_id: { $ne: service._id },
category: service.category,
isActive: true
})
.select('name shortDescription icon pricing')
.limit(3);
res.json({
success: true,
service,
relatedServices
});
} catch (error) {
console.error('Service detail API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching service'
});
}
});
// Get service categories
router.get('/meta/categories', async (req, res) => {
try {
const categories = await Service.distinct('category', { isActive: true });
// Get count for each category
const categoriesWithCount = await Promise.all(
categories.map(async (category) => {
const count = await Service.countDocuments({
category,
isActive: true
});
return { category, count };
})
);
res.json({
success: true,
categories: categoriesWithCount
});
} catch (error) {
console.error('Service categories API error:', error);
res.status(500).json({
success: false,
message: 'Error fetching categories'
});
}
});
// Search services
router.get('/search/:term', async (req, res) => {
try {
const searchTerm = req.params.term;
const limit = parseInt(req.query.limit) || 10;
const services = await Service.find({
$and: [
{ isActive: true },
{
$or: [
{ name: { $regex: searchTerm, $options: 'i' } },
{ description: { $regex: searchTerm, $options: 'i' } },
{ tags: { $in: [new RegExp(searchTerm, 'i')] } }
]
}
]
})
.select('name shortDescription icon pricing category')
.sort({ featured: -1, order: 1 })
.limit(limit);
res.json({
success: true,
services,
searchTerm,
count: services.length
});
} catch (error) {
console.error('Service search API error:', error);
res.status(500).json({
success: false,
message: 'Error searching services'
});
}
});
module.exports = router;

View File

@@ -0,0 +1,279 @@
#!/usr/bin/env node
/**
* Build script for production deployment
* Handles CSS compilation, asset optimization, and production setup
*/
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
// Configuration
const BUILD_DIR = path.join(__dirname, '..', 'dist');
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
const VIEWS_DIR = path.join(__dirname, '..', 'views');
async function buildForProduction() {
try {
console.log('🏗️ Starting production build...');
// Create build directory
await createBuildDirectory();
// Install production dependencies
await installProductionDependencies();
// Optimize assets
await optimizeAssets();
// Generate service worker with workbox
await generateServiceWorker();
// Create production environment file
await createProductionEnv();
// Copy necessary files
await copyProductionFiles();
console.log('✅ Production build completed successfully!');
console.log('📦 Build output available in:', BUILD_DIR);
console.log('🚀 Ready for deployment!');
} catch (error) {
console.error('❌ Production build failed:', error);
process.exit(1);
}
}
async function createBuildDirectory() {
try {
if (fs.existsSync(BUILD_DIR)) {
console.log('🗑️ Cleaning existing build directory...');
await execAsync(`rm -rf ${BUILD_DIR}`);
}
fs.mkdirSync(BUILD_DIR, { recursive: true });
console.log('📁 Created build directory');
} catch (error) {
console.error('❌ Error creating build directory:', error);
throw error;
}
}
async function installProductionDependencies() {
try {
console.log('📦 Installing production dependencies...');
await execAsync('npm ci --only=production');
console.log('✅ Production dependencies installed');
} catch (error) {
console.error('❌ Error installing dependencies:', error);
throw error;
}
}
async function optimizeAssets() {
try {
console.log('🎨 Optimizing CSS and JavaScript...');
// Create optimized CSS
const cssContent = fs.readFileSync(path.join(PUBLIC_DIR, 'css', 'main.css'), 'utf8');
const optimizedCSS = await optimizeCSS(cssContent);
const buildCSSDir = path.join(BUILD_DIR, 'public', 'css');
fs.mkdirSync(buildCSSDir, { recursive: true });
fs.writeFileSync(path.join(buildCSSDir, 'main.min.css'), optimizedCSS);
// Create optimized JavaScript
const jsContent = fs.readFileSync(path.join(PUBLIC_DIR, 'js', 'main.js'), 'utf8');
const optimizedJS = await optimizeJS(jsContent);
const buildJSDir = path.join(BUILD_DIR, 'public', 'js');
fs.mkdirSync(buildJSDir, { recursive: true });
fs.writeFileSync(path.join(buildJSDir, 'main.min.js'), optimizedJS);
// Copy other assets
await copyDirectory(path.join(PUBLIC_DIR, 'images'), path.join(BUILD_DIR, 'public', 'images'));
console.log('✅ Assets optimized');
} catch (error) {
console.error('❌ Error optimizing assets:', error);
throw error;
}
}
async function optimizeCSS(css) {
// Simple CSS minification (remove comments, extra whitespace)
return css
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
.replace(/\s{2,}/g, ' ') // Replace multiple spaces with single space
.replace(/;\s*}/g, '}') // Remove semicolon before closing brace
.replace(/\s*{\s*/g, '{') // Remove spaces around opening brace
.replace(/;\s*/g, ';') // Remove spaces after semicolons
.replace(/,\s*/g, ',') // Remove spaces after commas
.trim();
}
async function optimizeJS(js) {
// Simple JS minification (remove comments, extra whitespace)
return js
.replace(/\/\/.*$/gm, '') // Remove single-line comments
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
.replace(/\s{2,}/g, ' ') // Replace multiple spaces with single space
.replace(/\n\s*/g, '') // Remove newlines and indentation
.trim();
}
async function generateServiceWorker() {
try {
console.log('⚙️ Generating optimized service worker...');
const swContent = fs.readFileSync(path.join(PUBLIC_DIR, 'sw.js'), 'utf8');
const optimizedSW = await optimizeJS(swContent);
fs.writeFileSync(path.join(BUILD_DIR, 'public', 'sw.js'), optimizedSW);
// Copy manifest
fs.copyFileSync(
path.join(PUBLIC_DIR, 'manifest.json'),
path.join(BUILD_DIR, 'public', 'manifest.json')
);
console.log('✅ Service worker generated');
} catch (error) {
console.error('❌ Error generating service worker:', error);
throw error;
}
}
async function createProductionEnv() {
try {
console.log('🔧 Creating production environment configuration...');
const prodEnv = `
# Production Environment Configuration
NODE_ENV=production
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# Security
SESSION_SECRET=your-super-secret-session-key-change-this-in-production
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
# File Upload
UPLOAD_PATH=./uploads
MAX_FILE_SIZE=10485760
# Email Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
# Telegram Bot (Optional)
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
# Admin Account
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=change-this-password
# Security Headers
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
`.trim();
fs.writeFileSync(path.join(BUILD_DIR, '.env.production'), prodEnv);
console.log('✅ Production environment file created');
} catch (error) {
console.error('❌ Error creating production environment:', error);
throw error;
}
}
async function copyProductionFiles() {
try {
console.log('📋 Copying production files...');
// Copy main application files
const filesToCopy = [
'server.js',
'package.json',
'package-lock.json'
];
for (const file of filesToCopy) {
fs.copyFileSync(
path.join(__dirname, '..', file),
path.join(BUILD_DIR, file)
);
}
// Copy directories
await copyDirectory(
path.join(__dirname, '..', 'models'),
path.join(BUILD_DIR, 'models')
);
await copyDirectory(
path.join(__dirname, '..', 'routes'),
path.join(BUILD_DIR, 'routes')
);
await copyDirectory(
path.join(__dirname, '..', 'views'),
path.join(BUILD_DIR, 'views')
);
await copyDirectory(
path.join(__dirname, '..', 'middleware'),
path.join(BUILD_DIR, 'middleware')
);
// Create uploads directory
fs.mkdirSync(path.join(BUILD_DIR, 'uploads'), { recursive: true });
console.log('✅ Production files copied');
} catch (error) {
console.error('❌ Error copying production files:', error);
throw error;
}
}
async function copyDirectory(src, dest) {
try {
if (!fs.existsSync(src)) {
return;
}
fs.mkdirSync(dest, { recursive: true });
const items = fs.readdirSync(src);
for (const item of items) {
const srcPath = path.join(src, item);
const destPath = path.join(dest, item);
const stat = fs.statSync(srcPath);
if (stat.isDirectory()) {
await copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
} catch (error) {
console.error(`❌ Error copying directory ${src}:`, error);
throw error;
}
}
if (require.main === module) {
buildForProduction();
}
module.exports = { buildForProduction };

View File

@@ -0,0 +1,279 @@
#!/usr/bin/env node
/**
* Build script for production deployment
* Handles CSS compilation, asset optimization, and production setup
*/
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
// Configuration
const BUILD_DIR = path.join(__dirname, '..', 'dist');
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
const VIEWS_DIR = path.join(__dirname, '..', 'views');
async function buildForProduction() {
try {
console.log('🏗️ Starting production build...');
// Create build directory
await createBuildDirectory();
// Install production dependencies
await installProductionDependencies();
// Optimize assets
await optimizeAssets();
// Generate service worker with workbox
await generateServiceWorker();
// Create production environment file
await createProductionEnv();
// Copy necessary files
await copyProductionFiles();
console.log('✅ Production build completed successfully!');
console.log('📦 Build output available in:', BUILD_DIR);
console.log('🚀 Ready for deployment!');
} catch (error) {
console.error('❌ Production build failed:', error);
process.exit(1);
}
}
async function createBuildDirectory() {
try {
if (fs.existsSync(BUILD_DIR)) {
console.log('🗑️ Cleaning existing build directory...');
await execAsync(`rm -rf ${BUILD_DIR}`);
}
fs.mkdirSync(BUILD_DIR, { recursive: true });
console.log('📁 Created build directory');
} catch (error) {
console.error('❌ Error creating build directory:', error);
throw error;
}
}
async function installProductionDependencies() {
try {
console.log('📦 Installing production dependencies...');
await execAsync('npm ci --only=production');
console.log('✅ Production dependencies installed');
} catch (error) {
console.error('❌ Error installing dependencies:', error);
throw error;
}
}
async function optimizeAssets() {
try {
console.log('🎨 Optimizing CSS and JavaScript...');
// Create optimized CSS
const cssContent = fs.readFileSync(path.join(PUBLIC_DIR, 'css', 'main.css'), 'utf8');
const optimizedCSS = await optimizeCSS(cssContent);
const buildCSSDir = path.join(BUILD_DIR, 'public', 'css');
fs.mkdirSync(buildCSSDir, { recursive: true });
fs.writeFileSync(path.join(buildCSSDir, 'main.min.css'), optimizedCSS);
// Create optimized JavaScript
const jsContent = fs.readFileSync(path.join(PUBLIC_DIR, 'js', 'main.js'), 'utf8');
const optimizedJS = await optimizeJS(jsContent);
const buildJSDir = path.join(BUILD_DIR, 'public', 'js');
fs.mkdirSync(buildJSDir, { recursive: true });
fs.writeFileSync(path.join(buildJSDir, 'main.min.js'), optimizedJS);
// Copy other assets
await copyDirectory(path.join(PUBLIC_DIR, 'images'), path.join(BUILD_DIR, 'public', 'images'));
console.log('✅ Assets optimized');
} catch (error) {
console.error('❌ Error optimizing assets:', error);
throw error;
}
}
async function optimizeCSS(css) {
// Simple CSS minification (remove comments, extra whitespace)
return css
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
.replace(/\s{2,}/g, ' ') // Replace multiple spaces with single space
.replace(/;\s*}/g, '}') // Remove semicolon before closing brace
.replace(/\s*{\s*/g, '{') // Remove spaces around opening brace
.replace(/;\s*/g, ';') // Remove spaces after semicolons
.replace(/,\s*/g, ',') // Remove spaces after commas
.trim();
}
async function optimizeJS(js) {
// Simple JS minification (remove comments, extra whitespace)
return js
.replace(/\/\/.*$/gm, '') // Remove single-line comments
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
.replace(/\s{2,}/g, ' ') // Replace multiple spaces with single space
.replace(/\n\s*/g, '') // Remove newlines and indentation
.trim();
}
async function generateServiceWorker() {
try {
console.log('⚙️ Generating optimized service worker...');
const swContent = fs.readFileSync(path.join(PUBLIC_DIR, 'sw.js'), 'utf8');
const optimizedSW = await optimizeJS(swContent);
fs.writeFileSync(path.join(BUILD_DIR, 'public', 'sw.js'), optimizedSW);
// Copy manifest
fs.copyFileSync(
path.join(PUBLIC_DIR, 'manifest.json'),
path.join(BUILD_DIR, 'public', 'manifest.json')
);
console.log('✅ Service worker generated');
} catch (error) {
console.error('❌ Error generating service worker:', error);
throw error;
}
}
async function createProductionEnv() {
try {
console.log('🔧 Creating production environment configuration...');
const prodEnv = `
# Production Environment Configuration
NODE_ENV=production
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/smartsoltech
# Security
SESSION_SECRET=your-super-secret-session-key-change-this-in-production
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
# File Upload
UPLOAD_PATH=./uploads
MAX_FILE_SIZE=10485760
# Email Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
# Telegram Bot (Optional)
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
# Admin Account
ADMIN_EMAIL=admin@smartsoltech.kr
ADMIN_PASSWORD=change-this-password
# Security Headers
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
`.trim();
fs.writeFileSync(path.join(BUILD_DIR, '.env.production'), prodEnv);
console.log('✅ Production environment file created');
} catch (error) {
console.error('❌ Error creating production environment:', error);
throw error;
}
}
async function copyProductionFiles() {
try {
console.log('📋 Copying production files...');
// Copy main application files
const filesToCopy = [
'server.js',
'package.json',
'package-lock.json'
];
for (const file of filesToCopy) {
fs.copyFileSync(
path.join(__dirname, '..', file),
path.join(BUILD_DIR, file)
);
}
// Copy directories
await copyDirectory(
path.join(__dirname, '..', 'models'),
path.join(BUILD_DIR, 'models')
);
await copyDirectory(
path.join(__dirname, '..', 'routes'),
path.join(BUILD_DIR, 'routes')
);
await copyDirectory(
path.join(__dirname, '..', 'views'),
path.join(BUILD_DIR, 'views')
);
await copyDirectory(
path.join(__dirname, '..', 'middleware'),
path.join(BUILD_DIR, 'middleware')
);
// Create uploads directory
fs.mkdirSync(path.join(BUILD_DIR, 'uploads'), { recursive: true });
console.log('✅ Production files copied');
} catch (error) {
console.error('❌ Error copying production files:', error);
throw error;
}
}
async function copyDirectory(src, dest) {
try {
if (!fs.existsSync(src)) {
return;
}
fs.mkdirSync(dest, { recursive: true });
const items = fs.readdirSync(src);
for (const item of items) {
const srcPath = path.join(src, item);
const destPath = path.join(dest, item);
const stat = fs.statSync(srcPath);
if (stat.isDirectory()) {
await copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
} catch (error) {
console.error(`❌ Error copying directory ${src}:`, error);
throw error;
}
}
if (require.main === module) {
buildForProduction();
}
module.exports = { buildForProduction };

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Development server with hot reload for SmartSolTech
* Uses nodemon for automatic server restart on file changes
*/
const path = require('path');
const { spawn } = require('child_process');
// Configuration
const SERVER_FILE = path.join(__dirname, '..', 'server.js');
const NODEMON_CONFIG = {
script: SERVER_FILE,
ext: 'js,json,ejs',
ignore: ['node_modules/', 'public/', '.git/'],
watch: ['models/', 'routes/', 'views/', 'server.js'],
env: {
NODE_ENV: 'development',
DEBUG: 'app:*'
},
delay: 1000
};
function startDevelopmentServer() {
console.log('🚀 Starting SmartSolTech development server...');
console.log('📁 Watching files for changes...');
console.log('🔄 Server will automatically restart on file changes');
console.log('');
const nodemonArgs = [
'--script', NODEMON_CONFIG.script,
'--ext', NODEMON_CONFIG.ext,
'--ignore', NODEMON_CONFIG.ignore.join(','),
'--watch', NODEMON_CONFIG.watch.join(','),
'--delay', NODEMON_CONFIG.delay.toString()
];
const nodemon = spawn('npx', ['nodemon', ...nodemonArgs], {
stdio: 'inherit',
env: {
...process.env,
...NODEMON_CONFIG.env
}
});
nodemon.on('close', (code) => {
console.log(`\n🛑 Development server exited with code ${code}`);
process.exit(code);
});
nodemon.on('error', (error) => {
console.error('❌ Error starting development server:', error);
process.exit(1);
});
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down development server...');
nodemon.kill('SIGINT');
});
process.on('SIGTERM', () => {
console.log('\n🛑 Shutting down development server...');
nodemon.kill('SIGTERM');
});
}
if (require.main === module) {
startDevelopmentServer();
}
module.exports = { startDevelopmentServer };

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Development server with hot reload for SmartSolTech
* Uses nodemon for automatic server restart on file changes
*/
const path = require('path');
const { spawn } = require('child_process');
// Configuration
const SERVER_FILE = path.join(__dirname, '..', 'server.js');
const NODEMON_CONFIG = {
script: SERVER_FILE,
ext: 'js,json,ejs',
ignore: ['node_modules/', 'public/', '.git/'],
watch: ['models/', 'routes/', 'views/', 'server.js'],
env: {
NODE_ENV: 'development',
DEBUG: 'app:*'
},
delay: 1000
};
function startDevelopmentServer() {
console.log('🚀 Starting SmartSolTech development server...');
console.log('📁 Watching files for changes...');
console.log('🔄 Server will automatically restart on file changes');
console.log('');
const nodemonArgs = [
'--script', NODEMON_CONFIG.script,
'--ext', NODEMON_CONFIG.ext,
'--ignore', NODEMON_CONFIG.ignore.join(','),
'--watch', NODEMON_CONFIG.watch.join(','),
'--delay', NODEMON_CONFIG.delay.toString()
];
const nodemon = spawn('npx', ['nodemon', ...nodemonArgs], {
stdio: 'inherit',
env: {
...process.env,
...NODEMON_CONFIG.env
}
});
nodemon.on('close', (code) => {
console.log(`\n🛑 Development server exited with code ${code}`);
process.exit(code);
});
nodemon.on('error', (error) => {
console.error('❌ Error starting development server:', error);
process.exit(1);
});
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down development server...');
nodemon.kill('SIGINT');
});
process.on('SIGTERM', () => {
console.log('\n🛑 Shutting down development server...');
nodemon.kill('SIGTERM');
});
}
if (require.main === module) {
startDevelopmentServer();
}
module.exports = { startDevelopmentServer };

View File

@@ -0,0 +1,498 @@
#!/usr/bin/env node
/**
* Database initialization script for SmartSolTech
* Creates initial admin user and sample data
*/
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
require('dotenv').config();
// Import models
const User = require('../models/User');
const Service = require('../models/Service');
const Portfolio = require('../models/Portfolio');
const SiteSettings = require('../models/SiteSettings');
// Configuration
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@smartsoltech.kr';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123456';
async function initializeDatabase() {
try {
console.log('🔄 Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('✅ Connected to MongoDB');
// Create admin user
await createAdminUser();
// Create sample services
await createSampleServices();
// Create sample portfolio items
await createSamplePortfolio();
// Create site settings
await createSiteSettings();
console.log('🎉 Database initialization completed successfully!');
console.log(`📧 Admin login: ${ADMIN_EMAIL}`);
console.log(`🔑 Admin password: ${ADMIN_PASSWORD}`);
console.log('⚠️ Please change the admin password after first login!');
} catch (error) {
console.error('❌ Database initialization failed:', error);
process.exit(1);
} finally {
await mongoose.connection.close();
console.log('🔌 Database connection closed');
process.exit(0);
}
}
async function createAdminUser() {
try {
const existingAdmin = await User.findOne({ email: ADMIN_EMAIL });
if (existingAdmin) {
console.log('👤 Admin user already exists, skipping...');
return;
}
const adminUser = new User({
name: 'Administrator',
email: ADMIN_EMAIL,
password: ADMIN_PASSWORD,
role: 'admin',
isActive: true
});
await adminUser.save();
console.log('✅ Admin user created successfully');
} catch (error) {
console.error('❌ Error creating admin user:', error);
throw error;
}
}
async function createSampleServices() {
try {
const existingServices = await Service.countDocuments();
if (existingServices > 0) {
console.log('🛠️ Services already exist, skipping...');
return;
}
const services = [
{
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
features: [
{ name: '반응형 디자인', description: '모든 디바이스에서 완벽하게 작동', included: true },
{ name: 'SEO 최적화', description: '검색엔진 최적화로 더 많은 방문자 유치', included: true },
{ name: '성능 최적화', description: '빠른 로딩 속도와 원활한 사용자 경험', included: true },
{ name: '보안 강화', description: '최신 보안 기술로 안전한 웹사이트', included: true },
{ name: '유지보수', description: '6개월 무료 유지보수 지원', included: true }
],
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
estimatedTime: { min: 2, max: 8, unit: 'weeks' },
featured: true,
isActive: true,
order: 1,
tags: ['React', 'Node.js', 'MongoDB', 'Express', 'JavaScript', 'HTML', 'CSS']
},
{
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
features: [
{ name: '크로스플랫폼 개발', description: 'iOS와 Android 동시 지원', included: true },
{ name: '네이티브 성능', description: '최적화된 성능과 사용자 경험', included: true },
{ name: '푸시 알림', description: '실시간 푸시 알림 시스템', included: true },
{ name: '오프라인 지원', description: '인터넷 연결 없이도 기본 기능 사용 가능', included: false },
{ name: '앱스토어 등록', description: '앱스토어 및 플레이스토어 등록 지원', included: true }
],
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
estimatedTime: { min: 4, max: 16, unit: 'weeks' },
featured: true,
isActive: true,
order: 2,
tags: ['React Native', 'Flutter', 'iOS', 'Android', 'Swift', 'Kotlin', 'JavaScript']
},
{
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
features: [
{ name: '사용자 경험 연구', description: '타겟 사용자 분석 및 페르소나 설정', included: true },
{ name: '와이어프레임', description: '정보 구조와 레이아웃 설계', included: true },
{ name: '프로토타이핑', description: '인터랙티브 프로토타입 제작', included: true },
{ name: '비주얼 디자인', description: '브랜드에 맞는 시각적 디자인', included: true },
{ name: '디자인 시스템', description: '일관된 디자인을 위한 가이드라인', included: false }
],
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
estimatedTime: { min: 1, max: 6, unit: 'weeks' },
featured: true,
isActive: true,
order: 3,
tags: ['Figma', 'Sketch', 'Adobe XD', 'Photoshop', 'Illustrator', 'Prototyping']
},
{
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
features: [
{ name: 'SEO 최적화', description: '검색엔진 상위 노출을 위한 최적화', included: true },
{ name: '소셜미디어 관리', description: '페이스북, 인스타그램, 유튜브 관리', included: true },
{ name: '구글 광고', description: '구글 애즈를 통한 타겟 광고', included: true },
{ name: '콘텐츠 마케팅', description: '블로그 및 콘텐츠 제작', included: false },
{ name: '분석 및 리포팅', description: '마케팅 성과 분석 및 보고서', included: true }
],
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
estimatedTime: { min: 2, max: 12, unit: 'weeks' },
featured: true,
isActive: true,
order: 4,
tags: ['SEO', 'Google Ads', 'Facebook Ads', 'Analytics', 'Social Media', 'Content Marketing']
},
{
name: '브랜딩',
description: '기업의 정체성을 반영하는 로고, 브랜드 가이드라인, 마케팅 자료를 디자인합니다. 일관된 브랜드 이미지로 브랜드 가치를 높입니다.',
shortDescription: '로고, 브랜드 가이드라인, 마케팅 자료 디자인',
icon: 'fas fa-copyright',
category: 'design',
features: [
{ name: '로고 디자인', description: '기업 정체성을 반영한 로고 제작', included: true },
{ name: '브랜드 가이드라인', description: '색상, 폰트, 사용법 가이드', included: true },
{ name: '명함 디자인', description: '브랜드에 맞는 명함 디자인', included: true },
{ name: '브로슈어 디자인', description: '회사 소개 브로슈어 제작', included: false },
{ name: '웹 브랜딩', description: '웹사이트 브랜딩 요소 적용', included: false }
],
pricing: {
basePrice: 400000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 400000, max: 2500000 }
},
estimatedTime: { min: 2, max: 8, unit: 'weeks' },
featured: false,
isActive: true,
order: 5,
tags: ['Logo Design', 'Brand Identity', 'Graphic Design', 'Corporate Design']
},
{
name: '기술 컨설팅',
description: '기업의 디지털 전환과 기술 도입을 위한 전문적인 컨설팅을 제공합니다. 기술 아키텍처 설계부터 구현 전략까지 포괄적으로 지원합니다.',
shortDescription: '디지털 전환 및 기술 도입을 위한 전문 컨설팅',
icon: 'fas fa-lightbulb',
category: 'consulting',
features: [
{ name: '기술 분석', description: '현재 기술 스택 분석 및 개선안 제시', included: true },
{ name: '아키텍처 설계', description: '확장 가능한 시스템 아키텍처 설계', included: true },
{ name: '개발 프로세스 개선', description: '효율적인 개발 워크플로우 구축', included: true },
{ name: '팀 교육', description: '개발팀 대상 기술 교육', included: false },
{ name: '지속적인 지원', description: '프로젝트 완료 후 지속적인 기술 지원', included: false }
],
pricing: {
basePrice: 150000,
currency: 'KRW',
priceType: 'hourly',
priceRange: { min: 150000, max: 500000 }
},
estimatedTime: { min: 1, max: 4, unit: 'weeks' },
featured: false,
isActive: true,
order: 6,
tags: ['Architecture', 'Strategy', 'Process', 'Training', 'Consultation']
}
];
await Service.insertMany(services);
console.log('✅ Sample services created successfully');
} catch (error) {
console.error('❌ Error creating sample services:', error);
throw error;
}
}
async function createSamplePortfolio() {
try {
const existingPortfolio = await Portfolio.countDocuments();
if (existingPortfolio > 0) {
console.log('🎨 Portfolio items already exist, skipping...');
return;
}
const portfolioItems = [
{
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true },
{ url: '/images/portfolio/ecommerce-2.jpg', alt: '상품 상세페이지', isPrimary: false },
{ url: '/images/portfolio/ecommerce-3.jpg', alt: '장바구니 페이지', isPrimary: false }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.com',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25,
order: 1,
seo: {
metaTitle: 'E-commerce 플랫폼 개발 프로젝트',
metaDescription: '현대적인 온라인 쇼핑몰 플랫폼 개발 사례',
keywords: ['E-commerce', 'React', 'Node.js', '온라인쇼핑몰']
}
},
{
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true },
{ url: '/images/portfolio/fitness-2.jpg', alt: '운동 계획 화면', isPrimary: false },
{ url: '/images/portfolio/fitness-3.jpg', alt: '통계 화면', isPrimary: false }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35,
order: 2,
seo: {
metaTitle: '모바일 피트니스 앱 개발 프로젝트',
metaDescription: 'React Native로 개발한 크로스플랫폼 피트니스 앱',
keywords: ['Mobile App', 'React Native', 'Fitness', '헬스케어']
}
},
{
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true },
{ url: '/images/portfolio/corporate-2.jpg', alt: '회사소개 페이지', isPrimary: false },
{ url: '/images/portfolio/corporate-3.jpg', alt: '서비스 페이지', isPrimary: false }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.com',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18,
order: 3,
seo: {
metaTitle: '기업 웹사이트 리뉴얼 프로젝트',
metaDescription: '모던한 디자인과 향상된 UX의 기업 웹사이트',
keywords: ['Web Design', 'Corporate', 'UI/UX', '웹사이트리뉴얼']
}
},
{
title: '레스토랑 예약 시스템',
description: '레스토랑을 위한 온라인 예약 시스템을 개발했습니다. 실시간 테이블 현황, 예약 관리, 고객 관리 기능을 포함하여 레스토랑 운영 효율성을 높였습니다.',
shortDescription: '실시간 테이블 예약 및 관리 시스템',
category: 'web-development',
technologies: ['Vue.js', 'Laravel', 'MySQL', 'Socket.io', 'Bootstrap'],
images: [
{ url: '/images/portfolio/restaurant-1.jpg', alt: '예약 시스템 메인', isPrimary: true },
{ url: '/images/portfolio/restaurant-2.jpg', alt: '예약 현황 관리', isPrimary: false }
],
clientName: '레스토랑 델리셔스',
status: 'completed',
featured: false,
publishedAt: new Date('2024-04-05'),
completedAt: new Date('2024-04-01'),
isPublished: true,
viewCount: 85,
likes: 12,
order: 4,
seo: {
metaTitle: '레스토랑 예약 시스템 개발',
metaDescription: '실시간 테이블 예약 및 관리 웹 시스템',
keywords: ['Reservation System', 'Vue.js', 'Laravel', '예약시스템']
}
},
{
title: '교육 플랫폼 앱',
description: '온라인 교육을 위한 모바일 애플리케이션입니다. 동영상 강의, 퀴즈, 진도 관리 기능을 포함하여 효과적인 학습 환경을 제공합니다.',
shortDescription: '온라인 학습을 위한 교육 플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['Flutter', 'Dart', 'Firebase', 'FFmpeg', 'AWS S3'],
images: [
{ url: '/images/portfolio/education-1.jpg', alt: '교육 앱 메인화면', isPrimary: true },
{ url: '/images/portfolio/education-2.jpg', alt: '강의 재생 화면', isPrimary: false }
],
clientName: '온라인 교육 기업 EduTech',
status: 'completed',
featured: false,
publishedAt: new Date('2024-05-12'),
completedAt: new Date('2024-05-08'),
isPublished: true,
viewCount: 95,
likes: 20,
order: 5,
seo: {
metaTitle: '교육 플랫폼 모바일 앱 개발',
metaDescription: 'Flutter로 개발한 온라인 교육 모바일 앱',
keywords: ['Education App', 'Flutter', 'E-learning', '교육앱']
}
},
{
title: 'IoT 대시보드',
description: 'IoT 디바이스들을 모니터링하고 제어할 수 있는 웹 대시보드를 개발했습니다. 실시간 데이터 시각화와 알림 기능을 포함합니다.',
shortDescription: 'IoT 디바이스 모니터링 및 제어 웹 대시보드',
category: 'web-development',
technologies: ['React', 'D3.js', 'Node.js', 'MQTT', 'InfluxDB', 'Docker'],
images: [
{ url: '/images/portfolio/iot-1.jpg', alt: 'IoT 대시보드 메인', isPrimary: true }
],
clientName: 'IoT 솔루션 기업 SmartDevice',
status: 'in-progress',
featured: false,
publishedAt: new Date('2024-06-01'),
isPublished: true,
viewCount: 45,
likes: 8,
order: 6,
seo: {
metaTitle: 'IoT 대시보드 개발 프로젝트',
metaDescription: '실시간 IoT 디바이스 모니터링 웹 대시보드',
keywords: ['IoT', 'Dashboard', 'Real-time', 'Monitoring']
}
}
];
await Portfolio.insertMany(portfolioItems);
console.log('✅ Sample portfolio items created successfully');
} catch (error) {
console.error('❌ Error creating sample portfolio:', error);
throw error;
}
}
async function createSiteSettings() {
try {
const existingSettings = await SiteSettings.findOne();
if (existingSettings) {
console.log('⚙️ Site settings already exist, skipping...');
return;
}
const settings = new SiteSettings({
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
logo: '/images/logo.png',
favicon: '/images/favicon.ico',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech',
github: 'https://github.com/smartsoltech'
},
telegram: {
isEnabled: false
},
seo: {
metaTitle: 'SmartSolTech - 혁신적인 기술 솔루션',
metaDescription: '웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅 전문 기업. 한국에서 최고의 기술 솔루션을 제공합니다.',
keywords: '웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅, 기술 솔루션, 한국, SmartSolTech'
},
hero: {
title: 'Smart Technology Solutions',
subtitle: '혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다',
backgroundImage: '/images/hero-bg.jpg',
ctaText: '프로젝트 시작하기',
ctaLink: '/contact'
},
about: {
title: 'SmartSolTech 소개',
description: '우리는 최신 기술과 창의적인 아이디어로 고객의 비즈니스 성장을 지원하는 전문 개발팀입니다. 웹 개발부터 모바일 앱, UI/UX 디자인까지 포괄적인 디지털 솔루션을 제공합니다.',
image: '/images/about.jpg'
},
maintenance: {
isEnabled: false,
message: '현재 시스템 점검 중입니다. 잠시 후 다시 접속해 주세요.'
}
});
await settings.save();
console.log('✅ Site settings created successfully');
} catch (error) {
console.error('❌ Error creating site settings:', error);
throw error;
}
}
// Run the initialization
if (require.main === module) {
initializeDatabase();
}
module.exports = {
initializeDatabase,
createAdminUser,
createSampleServices,
createSamplePortfolio,
createSiteSettings
};

View File

@@ -0,0 +1,498 @@
#!/usr/bin/env node
/**
* Database initialization script for SmartSolTech
* Creates initial admin user and sample data
*/
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
require('dotenv').config();
// Import models
const User = require('../models/User');
const Service = require('../models/Service');
const Portfolio = require('../models/Portfolio');
const SiteSettings = require('../models/SiteSettings');
// Configuration
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@smartsoltech.kr';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123456';
async function initializeDatabase() {
try {
console.log('🔄 Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('✅ Connected to MongoDB');
// Create admin user
await createAdminUser();
// Create sample services
await createSampleServices();
// Create sample portfolio items
await createSamplePortfolio();
// Create site settings
await createSiteSettings();
console.log('🎉 Database initialization completed successfully!');
console.log(`📧 Admin login: ${ADMIN_EMAIL}`);
console.log(`🔑 Admin password: ${ADMIN_PASSWORD}`);
console.log('⚠️ Please change the admin password after first login!');
} catch (error) {
console.error('❌ Database initialization failed:', error);
process.exit(1);
} finally {
await mongoose.connection.close();
console.log('🔌 Database connection closed');
process.exit(0);
}
}
async function createAdminUser() {
try {
const existingAdmin = await User.findOne({ email: ADMIN_EMAIL });
if (existingAdmin) {
console.log('👤 Admin user already exists, skipping...');
return;
}
const adminUser = new User({
name: 'Administrator',
email: ADMIN_EMAIL,
password: ADMIN_PASSWORD,
role: 'admin',
isActive: true
});
await adminUser.save();
console.log('✅ Admin user created successfully');
} catch (error) {
console.error('❌ Error creating admin user:', error);
throw error;
}
}
async function createSampleServices() {
try {
const existingServices = await Service.countDocuments();
if (existingServices > 0) {
console.log('🛠️ Services already exist, skipping...');
return;
}
const services = [
{
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
features: [
{ name: '반응형 디자인', description: '모든 디바이스에서 완벽하게 작동', included: true },
{ name: 'SEO 최적화', description: '검색엔진 최적화로 더 많은 방문자 유치', included: true },
{ name: '성능 최적화', description: '빠른 로딩 속도와 원활한 사용자 경험', included: true },
{ name: '보안 강화', description: '최신 보안 기술로 안전한 웹사이트', included: true },
{ name: '유지보수', description: '6개월 무료 유지보수 지원', included: true }
],
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
estimatedTime: { min: 2, max: 8, unit: 'weeks' },
featured: true,
isActive: true,
order: 1,
tags: ['React', 'Node.js', 'MongoDB', 'Express', 'JavaScript', 'HTML', 'CSS']
},
{
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
features: [
{ name: '크로스플랫폼 개발', description: 'iOS와 Android 동시 지원', included: true },
{ name: '네이티브 성능', description: '최적화된 성능과 사용자 경험', included: true },
{ name: '푸시 알림', description: '실시간 푸시 알림 시스템', included: true },
{ name: '오프라인 지원', description: '인터넷 연결 없이도 기본 기능 사용 가능', included: false },
{ name: '앱스토어 등록', description: '앱스토어 및 플레이스토어 등록 지원', included: true }
],
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
estimatedTime: { min: 4, max: 16, unit: 'weeks' },
featured: true,
isActive: true,
order: 2,
tags: ['React Native', 'Flutter', 'iOS', 'Android', 'Swift', 'Kotlin', 'JavaScript']
},
{
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
features: [
{ name: '사용자 경험 연구', description: '타겟 사용자 분석 및 페르소나 설정', included: true },
{ name: '와이어프레임', description: '정보 구조와 레이아웃 설계', included: true },
{ name: '프로토타이핑', description: '인터랙티브 프로토타입 제작', included: true },
{ name: '비주얼 디자인', description: '브랜드에 맞는 시각적 디자인', included: true },
{ name: '디자인 시스템', description: '일관된 디자인을 위한 가이드라인', included: false }
],
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
estimatedTime: { min: 1, max: 6, unit: 'weeks' },
featured: true,
isActive: true,
order: 3,
tags: ['Figma', 'Sketch', 'Adobe XD', 'Photoshop', 'Illustrator', 'Prototyping']
},
{
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
features: [
{ name: 'SEO 최적화', description: '검색엔진 상위 노출을 위한 최적화', included: true },
{ name: '소셜미디어 관리', description: '페이스북, 인스타그램, 유튜브 관리', included: true },
{ name: '구글 광고', description: '구글 애즈를 통한 타겟 광고', included: true },
{ name: '콘텐츠 마케팅', description: '블로그 및 콘텐츠 제작', included: false },
{ name: '분석 및 리포팅', description: '마케팅 성과 분석 및 보고서', included: true }
],
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
estimatedTime: { min: 2, max: 12, unit: 'weeks' },
featured: true,
isActive: true,
order: 4,
tags: ['SEO', 'Google Ads', 'Facebook Ads', 'Analytics', 'Social Media', 'Content Marketing']
},
{
name: '브랜딩',
description: '기업의 정체성을 반영하는 로고, 브랜드 가이드라인, 마케팅 자료를 디자인합니다. 일관된 브랜드 이미지로 브랜드 가치를 높입니다.',
shortDescription: '로고, 브랜드 가이드라인, 마케팅 자료 디자인',
icon: 'fas fa-copyright',
category: 'design',
features: [
{ name: '로고 디자인', description: '기업 정체성을 반영한 로고 제작', included: true },
{ name: '브랜드 가이드라인', description: '색상, 폰트, 사용법 가이드', included: true },
{ name: '명함 디자인', description: '브랜드에 맞는 명함 디자인', included: true },
{ name: '브로슈어 디자인', description: '회사 소개 브로슈어 제작', included: false },
{ name: '웹 브랜딩', description: '웹사이트 브랜딩 요소 적용', included: false }
],
pricing: {
basePrice: 400000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 400000, max: 2500000 }
},
estimatedTime: { min: 2, max: 8, unit: 'weeks' },
featured: false,
isActive: true,
order: 5,
tags: ['Logo Design', 'Brand Identity', 'Graphic Design', 'Corporate Design']
},
{
name: '기술 컨설팅',
description: '기업의 디지털 전환과 기술 도입을 위한 전문적인 컨설팅을 제공합니다. 기술 아키텍처 설계부터 구현 전략까지 포괄적으로 지원합니다.',
shortDescription: '디지털 전환 및 기술 도입을 위한 전문 컨설팅',
icon: 'fas fa-lightbulb',
category: 'consulting',
features: [
{ name: '기술 분석', description: '현재 기술 스택 분석 및 개선안 제시', included: true },
{ name: '아키텍처 설계', description: '확장 가능한 시스템 아키텍처 설계', included: true },
{ name: '개발 프로세스 개선', description: '효율적인 개발 워크플로우 구축', included: true },
{ name: '팀 교육', description: '개발팀 대상 기술 교육', included: false },
{ name: '지속적인 지원', description: '프로젝트 완료 후 지속적인 기술 지원', included: false }
],
pricing: {
basePrice: 150000,
currency: 'KRW',
priceType: 'hourly',
priceRange: { min: 150000, max: 500000 }
},
estimatedTime: { min: 1, max: 4, unit: 'weeks' },
featured: false,
isActive: true,
order: 6,
tags: ['Architecture', 'Strategy', 'Process', 'Training', 'Consultation']
}
];
await Service.insertMany(services);
console.log('✅ Sample services created successfully');
} catch (error) {
console.error('❌ Error creating sample services:', error);
throw error;
}
}
async function createSamplePortfolio() {
try {
const existingPortfolio = await Portfolio.countDocuments();
if (existingPortfolio > 0) {
console.log('🎨 Portfolio items already exist, skipping...');
return;
}
const portfolioItems = [
{
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true },
{ url: '/images/portfolio/ecommerce-2.jpg', alt: '상품 상세페이지', isPrimary: false },
{ url: '/images/portfolio/ecommerce-3.jpg', alt: '장바구니 페이지', isPrimary: false }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.com',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25,
order: 1,
seo: {
metaTitle: 'E-commerce 플랫폼 개발 프로젝트',
metaDescription: '현대적인 온라인 쇼핑몰 플랫폼 개발 사례',
keywords: ['E-commerce', 'React', 'Node.js', '온라인쇼핑몰']
}
},
{
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true },
{ url: '/images/portfolio/fitness-2.jpg', alt: '운동 계획 화면', isPrimary: false },
{ url: '/images/portfolio/fitness-3.jpg', alt: '통계 화면', isPrimary: false }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35,
order: 2,
seo: {
metaTitle: '모바일 피트니스 앱 개발 프로젝트',
metaDescription: 'React Native로 개발한 크로스플랫폼 피트니스 앱',
keywords: ['Mobile App', 'React Native', 'Fitness', '헬스케어']
}
},
{
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true },
{ url: '/images/portfolio/corporate-2.jpg', alt: '회사소개 페이지', isPrimary: false },
{ url: '/images/portfolio/corporate-3.jpg', alt: '서비스 페이지', isPrimary: false }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.com',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18,
order: 3,
seo: {
metaTitle: '기업 웹사이트 리뉴얼 프로젝트',
metaDescription: '모던한 디자인과 향상된 UX의 기업 웹사이트',
keywords: ['Web Design', 'Corporate', 'UI/UX', '웹사이트리뉴얼']
}
},
{
title: '레스토랑 예약 시스템',
description: '레스토랑을 위한 온라인 예약 시스템을 개발했습니다. 실시간 테이블 현황, 예약 관리, 고객 관리 기능을 포함하여 레스토랑 운영 효율성을 높였습니다.',
shortDescription: '실시간 테이블 예약 및 관리 시스템',
category: 'web-development',
technologies: ['Vue.js', 'Laravel', 'MySQL', 'Socket.io', 'Bootstrap'],
images: [
{ url: '/images/portfolio/restaurant-1.jpg', alt: '예약 시스템 메인', isPrimary: true },
{ url: '/images/portfolio/restaurant-2.jpg', alt: '예약 현황 관리', isPrimary: false }
],
clientName: '레스토랑 델리셔스',
status: 'completed',
featured: false,
publishedAt: new Date('2024-04-05'),
completedAt: new Date('2024-04-01'),
isPublished: true,
viewCount: 85,
likes: 12,
order: 4,
seo: {
metaTitle: '레스토랑 예약 시스템 개발',
metaDescription: '실시간 테이블 예약 및 관리 웹 시스템',
keywords: ['Reservation System', 'Vue.js', 'Laravel', '예약시스템']
}
},
{
title: '교육 플랫폼 앱',
description: '온라인 교육을 위한 모바일 애플리케이션입니다. 동영상 강의, 퀴즈, 진도 관리 기능을 포함하여 효과적인 학습 환경을 제공합니다.',
shortDescription: '온라인 학습을 위한 교육 플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['Flutter', 'Dart', 'Firebase', 'FFmpeg', 'AWS S3'],
images: [
{ url: '/images/portfolio/education-1.jpg', alt: '교육 앱 메인화면', isPrimary: true },
{ url: '/images/portfolio/education-2.jpg', alt: '강의 재생 화면', isPrimary: false }
],
clientName: '온라인 교육 기업 EduTech',
status: 'completed',
featured: false,
publishedAt: new Date('2024-05-12'),
completedAt: new Date('2024-05-08'),
isPublished: true,
viewCount: 95,
likes: 20,
order: 5,
seo: {
metaTitle: '교육 플랫폼 모바일 앱 개발',
metaDescription: 'Flutter로 개발한 온라인 교육 모바일 앱',
keywords: ['Education App', 'Flutter', 'E-learning', '교육앱']
}
},
{
title: 'IoT 대시보드',
description: 'IoT 디바이스들을 모니터링하고 제어할 수 있는 웹 대시보드를 개발했습니다. 실시간 데이터 시각화와 알림 기능을 포함합니다.',
shortDescription: 'IoT 디바이스 모니터링 및 제어 웹 대시보드',
category: 'web-development',
technologies: ['React', 'D3.js', 'Node.js', 'MQTT', 'InfluxDB', 'Docker'],
images: [
{ url: '/images/portfolio/iot-1.jpg', alt: 'IoT 대시보드 메인', isPrimary: true }
],
clientName: 'IoT 솔루션 기업 SmartDevice',
status: 'in-progress',
featured: false,
publishedAt: new Date('2024-06-01'),
isPublished: true,
viewCount: 45,
likes: 8,
order: 6,
seo: {
metaTitle: 'IoT 대시보드 개발 프로젝트',
metaDescription: '실시간 IoT 디바이스 모니터링 웹 대시보드',
keywords: ['IoT', 'Dashboard', 'Real-time', 'Monitoring']
}
}
];
await Portfolio.insertMany(portfolioItems);
console.log('✅ Sample portfolio items created successfully');
} catch (error) {
console.error('❌ Error creating sample portfolio:', error);
throw error;
}
}
async function createSiteSettings() {
try {
const existingSettings = await SiteSettings.findOne();
if (existingSettings) {
console.log('⚙️ Site settings already exist, skipping...');
return;
}
const settings = new SiteSettings({
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
logo: '/images/logo.png',
favicon: '/images/favicon.ico',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech',
github: 'https://github.com/smartsoltech'
},
telegram: {
isEnabled: false
},
seo: {
metaTitle: 'SmartSolTech - 혁신적인 기술 솔루션',
metaDescription: '웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅 전문 기업. 한국에서 최고의 기술 솔루션을 제공합니다.',
keywords: '웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅, 기술 솔루션, 한국, SmartSolTech'
},
hero: {
title: 'Smart Technology Solutions',
subtitle: '혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다',
backgroundImage: '/images/hero-bg.jpg',
ctaText: '프로젝트 시작하기',
ctaLink: '/contact'
},
about: {
title: 'SmartSolTech 소개',
description: '우리는 최신 기술과 창의적인 아이디어로 고객의 비즈니스 성장을 지원하는 전문 개발팀입니다. 웹 개발부터 모바일 앱, UI/UX 디자인까지 포괄적인 디지털 솔루션을 제공합니다.',
image: '/images/about.jpg'
},
maintenance: {
isEnabled: false,
message: '현재 시스템 점검 중입니다. 잠시 후 다시 접속해 주세요.'
}
});
await settings.save();
console.log('✅ Site settings created successfully');
} catch (error) {
console.error('❌ Error creating site settings:', error);
throw error;
}
}
// Run the initialization
if (require.main === module) {
initializeDatabase();
}
module.exports = {
initializeDatabase,
createAdminUser,
createSampleServices,
createSamplePortfolio,
createSiteSettings
};

View File

@@ -0,0 +1,416 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

View File

@@ -0,0 +1,416 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

View File

@@ -0,0 +1,418 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.',
currentPage: '404'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.',
currentPage: 'error'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

View File

@@ -0,0 +1,418 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.',
currentPage: '404'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.',
currentPage: 'error'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

View File

@@ -0,0 +1,419 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects,
currentPage: 'portfolio'
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.',
currentPage: '404'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.',
currentPage: 'error'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

View File

@@ -0,0 +1,419 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects,
currentPage: 'portfolio'
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.',
currentPage: '404'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.',
currentPage: 'error'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

View File

@@ -0,0 +1,426 @@
/**
* Demo server for SmartSolTech website
* Uses mock data instead of MongoDB for demonstration
*/
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const flash = require('connect-flash');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com"],
imgSrc: ["'self'", "data:", "https:", "blob:"],
fontSrc: ["'self'", "https://cdnjs.cloudflare.com"],
connectSrc: ["'self'", "https:"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Compression
app.use(compression());
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Session configuration
app.use(session({
secret: 'demo-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true in production with HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Flash messages
app.use(flash());
// Global variables for templates
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
res.locals.error = req.flash('error');
res.locals.currentYear = new Date().getFullYear();
next();
});
// Mock data
const mockPortfolio = [
{
_id: '1',
title: 'E-commerce 플랫폼',
description: '현대적인 온라인 쇼핑몰 플랫폼을 개발했습니다. React와 Node.js를 활용하여 높은 성능과 사용자 경험을 제공하며, 결제 시스템과 재고 관리 기능을 포함합니다.',
shortDescription: '반응형 온라인 쇼핑몰 플랫폼 개발',
category: 'web-development',
technologies: ['React', 'Node.js', 'MongoDB', 'Express', 'Stripe', 'AWS'],
images: [
{ url: '/images/portfolio/ecommerce-1.jpg', alt: 'E-commerce 메인페이지', isPrimary: true }
],
clientName: '패션 브랜드 ABC',
projectUrl: 'https://example-ecommerce.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-01-15'),
completedAt: new Date('2024-01-10'),
isPublished: true,
viewCount: 150,
likes: 25
},
{
_id: '2',
title: '모바일 피트니스 앱',
description: 'React Native를 사용하여 크로스플랫폼 피트니스 애플리케이션을 개발했습니다. 운동 계획, 칼로리 추적, 소셜 기능을 포함하여 사용자들의 건강한 라이프스타일을 지원합니다.',
shortDescription: '건강 관리를 위한 크로스플랫폼 모바일 앱',
category: 'mobile-app',
technologies: ['React Native', 'Redux', 'Firebase', 'Node.js', 'PostgreSQL'],
images: [
{ url: '/images/portfolio/fitness-1.jpg', alt: '피트니스 앱 메인화면', isPrimary: true }
],
clientName: '헬스케어 스타트업 FIT',
status: 'completed',
featured: true,
publishedAt: new Date('2024-02-20'),
completedAt: new Date('2024-02-15'),
isPublished: true,
viewCount: 200,
likes: 35
},
{
_id: '3',
title: '기업 웹사이트 리뉴얼',
description: '기업의 브랜드 아이덴티티를 반영한 웹사이트 리뉴얼 프로젝트입니다. 사용자 경험을 개선하고 모던한 디자인을 적용하여 브랜드 가치를 높였습니다.',
shortDescription: '브랜드 아이덴티티를 반영한 기업 웹사이트 리뉴얼',
category: 'ui-ux-design',
technologies: ['Figma', 'React', 'Sass', 'Framer Motion', 'Contentful'],
images: [
{ url: '/images/portfolio/corporate-1.jpg', alt: '기업 웹사이트 메인페이지', isPrimary: true }
],
clientName: '기술 기업 TechCorp',
projectUrl: 'https://example-corp.demo',
status: 'completed',
featured: true,
publishedAt: new Date('2024-03-10'),
completedAt: new Date('2024-03-05'),
isPublished: true,
viewCount: 120,
likes: 18
}
];
const mockServices = [
{
_id: '1',
name: '웹 개발',
description: '현대적이고 반응형인 웹사이트와 웹 애플리케이션을 개발합니다. React, Node.js, MongoDB 등 최신 기술 스택을 활용하여 성능과 사용자 경험을 최적화합니다.',
shortDescription: '현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발',
icon: 'fas fa-code',
category: 'development',
pricing: {
basePrice: 500000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 500000, max: 5000000 }
},
featured: true,
isActive: true
},
{
_id: '2',
name: '모바일 앱 개발',
description: 'iOS와 Android 플랫폼을 위한 네이티브 및 크로스플랫폼 모바일 애플리케이션을 개발합니다. React Native, Flutter 등을 활용하여 효율적인 개발을 진행합니다.',
shortDescription: 'iOS/Android 네이티브 및 크로스플랫폼 앱 개발',
icon: 'fas fa-mobile-alt',
category: 'development',
pricing: {
basePrice: 800000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 800000, max: 8000000 }
},
featured: true,
isActive: true
},
{
_id: '3',
name: 'UI/UX 디자인',
description: '사용자 중심의 직관적이고 아름다운 인터페이스를 디자인합니다. 사용자 경험 연구와 프로토타이핑을 통해 최적의 디자인 솔루션을 제공합니다.',
shortDescription: '사용자 중심의 직관적이고 아름다운 인터페이스 디자인',
icon: 'fas fa-palette',
category: 'design',
pricing: {
basePrice: 300000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 300000, max: 2000000 }
},
featured: true,
isActive: true
},
{
_id: '4',
name: '디지털 마케팅',
description: 'SEO, 소셜미디어 마케팅, 온라인 광고를 통해 디지털 마케팅 전략을 수립하고 실행합니다. 데이터 분석을 통한 지속적인 최적화를 제공합니다.',
shortDescription: 'SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅',
icon: 'fas fa-chart-line',
category: 'marketing',
pricing: {
basePrice: 200000,
currency: 'KRW',
priceType: 'project',
priceRange: { min: 200000, max: 1500000 }
},
featured: true,
isActive: true
}
];
const mockSettings = {
siteName: 'SmartSolTech',
siteDescription: '혁신적인 기술 솔루션으로 비즈니스의 성장을 지원합니다',
contact: {
email: 'info@smartsoltech.kr',
phone: '+82-10-1234-5678',
address: 'Seoul, South Korea'
},
social: {
facebook: 'https://facebook.com/smartsoltech',
twitter: 'https://twitter.com/smartsoltech',
linkedin: 'https://linkedin.com/company/smartsoltech',
instagram: 'https://instagram.com/smartsoltech'
}
};
// Helper function for category names
function getCategoryName(category) {
const categoryNames = {
'web-development': '웹 개발',
'mobile-app': '모바일 앱',
'ui-ux-design': 'UI/UX 디자인',
'branding': '브랜딩',
'marketing': '디지털 마케팅'
};
return categoryNames[category] || category;
}
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'SmartSolTech - Innovative Technology Solutions',
settings: mockSettings,
featuredPortfolio: mockPortfolio.filter(p => p.featured),
featuredServices: mockServices.filter(s => s.featured),
currentPage: 'home'
});
});
app.get('/portfolio', (req, res) => {
const category = req.query.category;
let filteredPortfolio = mockPortfolio;
if (category && category !== 'all') {
filteredPortfolio = mockPortfolio.filter(p => p.category === category);
}
res.render('portfolio', {
title: '포트폴리오 - SmartSolTech',
portfolioItems: filteredPortfolio,
currentPage: 'portfolio'
});
});
app.get('/portfolio/:id', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (!portfolio) {
return res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 포트폴리오 항목을 찾을 수 없습니다.'
});
}
const relatedProjects = mockPortfolio.filter(p =>
p._id !== portfolio._id && p.category === portfolio.category
).slice(0, 3);
res.render('portfolio-detail', {
title: `${portfolio.title} - 포트폴리오`,
portfolio,
relatedProjects,
currentPage: 'portfolio'
});
});
app.get('/services', (req, res) => {
res.render('services', {
title: '서비스 - SmartSolTech',
services: mockServices,
currentPage: 'services'
});
});
app.get('/about', (req, res) => {
res.render('about', {
title: '회사 소개 - SmartSolTech',
currentPage: 'about'
});
});
app.get('/contact', (req, res) => {
res.render('contact', {
title: '연락처 - SmartSolTech',
settings: mockSettings,
currentPage: 'contact'
});
});
app.post('/contact', (req, res) => {
// Simulate contact form processing
console.log('Contact form submission:', req.body);
req.flash('success', '문의가 성공적으로 접수되었습니다. 빠른 시일 내에 답변드리겠습니다.');
res.redirect('/contact');
});
app.get('/calculator', (req, res) => {
res.render('calculator', {
title: '비용 계산기 - SmartSolTech',
services: mockServices,
currentPage: 'calculator'
});
});
// API Routes for calculator
app.post('/api/calculator/estimate', (req, res) => {
const { service, projectType, timeline, features } = req.body;
// Simple calculation logic
let basePrice = 500000;
const selectedService = mockServices.find(s => s._id === service);
if (selectedService) {
basePrice = selectedService.pricing.basePrice;
}
// Apply multipliers based on project complexity
const typeMultipliers = {
'simple': 1,
'medium': 1.5,
'complex': 2.5,
'enterprise': 4
};
const timelineMultipliers = {
'urgent': 1.5,
'normal': 1,
'flexible': 0.9
};
const typeMultiplier = typeMultipliers[projectType] || 1;
const timelineMultiplier = timelineMultipliers[timeline] || 1;
const featuresMultiplier = 1 + (features?.length || 0) * 0.2;
const estimatedPrice = Math.round(basePrice * typeMultiplier * timelineMultiplier * featuresMultiplier);
res.json({
success: true,
estimate: {
basePrice,
estimatedPrice,
breakdown: {
basePrice,
projectType: projectType,
typeMultiplier,
timeline,
timelineMultiplier,
features: features || [],
featuresMultiplier
}
}
});
});
// API Routes for portfolio interactions
app.post('/api/portfolio/:id/like', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.likes = (portfolio.likes || 0) + 1;
res.json({ success: true, likes: portfolio.likes });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
app.post('/api/portfolio/:id/view', (req, res) => {
const portfolio = mockPortfolio.find(p => p._id === req.params.id);
if (portfolio) {
portfolio.viewCount = (portfolio.viewCount || 0) + 1;
res.json({ success: true, viewCount: portfolio.viewCount });
} else {
res.status(404).json({ success: false, message: 'Portfolio not found' });
}
});
// Error handling
app.use((req, res) => {
res.status(404).render('error', {
title: '404 - 페이지를 찾을 수 없습니다',
message: '요청하신 페이지를 찾을 수 없습니다.',
currentPage: '404'
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', {
title: '500 - 서버 오류',
message: '서버에서 오류가 발생했습니다.',
currentPage: 'error'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 SmartSolTech Demo Server running on http://localhost:${PORT}`);
console.log('📋 Available pages:');
console.log(' • Home: http://localhost:3000/');
console.log(' • Portfolio: http://localhost:3000/portfolio');
console.log(' • Services: http://localhost:3000/services');
console.log(' • Contact: http://localhost:3000/contact');
console.log(' • Calculator: http://localhost:3000/calculator');
console.log('');
console.log('💡 This is a demo version using mock data');
console.log('💾 To use with MongoDB, run: npm start');
});
module.exports = app;

Some files were not shown because too many files have changed in this diff Show More