diff --git a/.history/.env_20251019160502.example b/.history/.env_20251019160502.example deleted file mode 100644 index bbf9a40..0000000 --- a/.history/.env_20251019160502.example +++ /dev/null @@ -1,34 +0,0 @@ -# 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 \ No newline at end of file diff --git a/.history/.env_20251019162544.example b/.history/.env_20251019162544.example deleted file mode 100644 index bbf9a40..0000000 --- a/.history/.env_20251019162544.example +++ /dev/null @@ -1,34 +0,0 @@ -# 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 \ No newline at end of file diff --git a/.history/.env_20251019201239 b/.history/.env_20251019201239 deleted file mode 100644 index bbf9a40..0000000 --- a/.history/.env_20251019201239 +++ /dev/null @@ -1,34 +0,0 @@ -# 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 \ No newline at end of file diff --git a/.history/.env_20251019201325 b/.history/.env_20251019201325 deleted file mode 100644 index 71be5b1..0000000 --- a/.history/.env_20251019201325 +++ /dev/null @@ -1,34 +0,0 @@ -# Environment Configuration -NODE_ENV=development -PORT=3000 - -# Database -MONGODB_URI=mongodb://localhost:27017/smartsoltech - -# JWT Secret -JWT_SECRET=:AISDFUhbsLKFJADESFIOlugtywow8fryuelhbf2p349[yresdugiflhbjKASDJfbh - -# Session Secret -SESSION_SECRET=LKSJ:DFHGAJKLfbads;oflIAFK:DHJSUGBVdil;asufgtdshjfv - -# 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 \ No newline at end of file diff --git a/.history/.env_20251019201339 b/.history/.env_20251019201339 deleted file mode 100644 index d0b7103..0000000 --- a/.history/.env_20251019201339 +++ /dev/null @@ -1,34 +0,0 @@ -# Environment Configuration -NODE_ENV=development -PORT=3000 - -# Database -MONGODB_URI=mongodb://localhost:27017/smartsoltech - -# JWT Secret -JWT_SECRET=:AISDFUhbsLKFJADESFIOlugtywow8fryuelhbf2p349[yresdugiflhbjKASDJfbh - -# Session Secret -SESSION_SECRET=LKSJ:DFHGAJKLfbads;oflIAFK:DHJSUGBVdil;asufgtdshjfv - -# Telegram Bot -TELEGRAM_BOT_TOKEN=8289431590:AAEVZWjb6PgGbv-jivphXfqCMD9c6NqE870 -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 \ No newline at end of file diff --git a/.history/.env_20251019201413 b/.history/.env_20251019201413 deleted file mode 100644 index 2387270..0000000 --- a/.history/.env_20251019201413 +++ /dev/null @@ -1,34 +0,0 @@ -# Environment Configuration -NODE_ENV=development -PORT=3000 - -# Database -MONGODB_URI=mongodb://localhost:27017/smartsoltech - -# JWT Secret -JWT_SECRET=:AISDFUhbsLKFJADESFIOlugtywow8fryuelhbf2p349[yresdugiflhbjKASDJfbh - -# Session Secret -SESSION_SECRET=LKSJ:DFHGAJKLfbads;oflIAFK:DHJSUGBVdil;asufgtdshjfv - -# Telegram Bot -TELEGRAM_BOT_TOKEN=8289431590:AAEVZWjb6PgGbv-jivphXfqCMD9c6NqE870 -TELEGRAM_CHAT_ID=-4949859416 - -# 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 \ No newline at end of file diff --git a/.history/.env_20251019201552 b/.history/.env_20251019201552 deleted file mode 100644 index 5f88f82..0000000 --- a/.history/.env_20251019201552 +++ /dev/null @@ -1,39 +0,0 @@ -# Environment Configuration -NODE_ENV=development -PORT=3000 - -# Database - PostgreSQL -DATABASE_URL=postgresql://postgres:password@localhost:5432/smartsoltech -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=smartsoltech -DB_USER=postgres -DB_PASSWORD=password - -# JWT Secret -JWT_SECRET=:AISDFUhbsLKFJADESFIOlugtywow8fryuelhbf2p349[yresdugiflhbjKASDJfbh - -# Session Secret -SESSION_SECRET=LKSJ:DFHGAJKLfbads;oflIAFK:DHJSUGBVdil;asufgtdshjfv - -# Telegram Bot -TELEGRAM_BOT_TOKEN=8289431590:AAEVZWjb6PgGbv-jivphXfqCMD9c6NqE870 -TELEGRAM_CHAT_ID=-4949859416 - -# 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 \ No newline at end of file diff --git a/.history/.env_20251019201634 b/.history/.env_20251019201634 deleted file mode 100644 index 036433e..0000000 --- a/.history/.env_20251019201634 +++ /dev/null @@ -1,39 +0,0 @@ -# Environment Configuration -NODE_ENV=development -PORT=3000 - -# Database - PostgreSQL -DATABASE_URL=postgresql://trevor:R0sebud@localhost:5432/smartsoltech -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=smartsoltech -DB_USER=trevor -DB_PASSWORD=R0sebud - -# JWT Secret -JWT_SECRET=:AISDFUhbsLKFJADESFIOlugtywow8fryuelhbf2p349[yresdugiflhbjKASDJfbh - -# Session Secret -SESSION_SECRET=LKSJ:DFHGAJKLfbads;oflIAFK:DHJSUGBVdil;asufgtdshjfv - -# Telegram Bot -TELEGRAM_BOT_TOKEN=8289431590:AAEVZWjb6PgGbv-jivphXfqCMD9c6NqE870 -TELEGRAM_CHAT_ID=-4949859416 - -# 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 \ No newline at end of file diff --git a/.history/.env_20251021214349 b/.history/.env_20251021214349 deleted file mode 100644 index 6cf440b..0000000 --- a/.history/.env_20251021214349 +++ /dev/null @@ -1,39 +0,0 @@ -# Environment Configuration -NODE_ENV=development -PORT=3000 - -# Database - PostgreSQL -DATABASE_URL=postgresql://trevor:R0sebud@localhost:5432/smartsoltech -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=smartsoltech -DB_USER=trevor -DB_PASSWORD=R0sebud - -# JWT Secret -JWT_SECRET=:AISDFUhbsLKFJADESFIOlugtywow8fryuelhbf2p349[yresdugiflhbjKASDJfbh - -# Session Secret -SESSION_SECRET=LKSJ:DFHGAJKLfbads;oflIAFK:DHJSUGBVdil;asufgtdshjfv - -# Telegram Bot -TELEGRAM_BOT_TOKEN=8289431590:AAEVZWjb6PgGbv-jivphXfqCMD9c6NqE870 -TELEGRAM_CHAT_ID=-4949859416 - -# Email Configuration (for contact forms) -EMAIL_HOST=smtp.gmail.com -EMAIL_PORT=587 -EMAIL_USER=tsoy4uk@gmail.com -EMAIL_PASS=Cl0ud_1985_! - -# 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 \ No newline at end of file diff --git a/.history/.github/copilot-instructions_20251019201336.md b/.history/.github/copilot-instructions_20251019201336.md deleted file mode 100644 index c35af6b..0000000 --- a/.history/.github/copilot-instructions_20251019201336.md +++ /dev/null @@ -1,151 +0,0 @@ -# SmartSolTech Website - AI Coding Agent Instructions - -## Project Overview -SmartSolTech is a **Korean tech services company** PWA with an admin panel, portfolio management, Telegram integration, and service calculator. Built with Node.js/Express backend, EJS templating, MongoDB, and modern PWA features. - -## Architecture & Key Patterns - -### 🏗️ Backend Architecture -- **Main server**: `server.js` - Express app with comprehensive middleware stack -- **Models**: MongoDB with Mongoose ODM - all in `/models/` (User, Portfolio, Service, Contact, SiteSettings) -- **Routes**: RESTful API patterns in `/routes/` - separate files per domain -- **Authentication**: Dual auth system - JWT tokens for API, sessions for web pages -- **Validation**: express-validator middleware in `/middleware/validation.js` with Korean error messages - -### 🔑 Authentication Pattern -```javascript -// Two authentication strategies: -// 1. JWT for API endpoints (authenticateToken) -// 2. Session-based for web pages (authenticateSession) -// See middleware/auth.js for implementations -``` - -### 📊 Data Models Key Features -- **Portfolio**: Has virtual `primaryImage` getter, text search indexes, and SEO fields -- **Service**: Complex pricing structure with features array (included/not included) -- **User**: bcrypt password hashing with pre-save hook and comparePassword method -- **All models**: Use timestamps and have soft-delete patterns where applicable - -### 🛣️ Route Organization -- **Admin routes** (`/admin`): Session-based auth, server-side rendered EJS views -- **API routes** (`/api/*`): JWT auth, JSON responses, rate-limited -- **Public routes** (`/`): Mixed auth (optional), EJS templates with PWA support - -## Development Workflow - -### 🚀 Essential Commands -```bash -# Development with hot reload and file watching -npm run dev - -# Initialize database with admin user and sample data -npm run init-db - -# Production build with webpack optimization -npm run build - -# Database setup creates: -# - Admin user (from .env: ADMIN_EMAIL/ADMIN_PASSWORD) -# - Sample services, portfolio items, site settings -``` - -### 🔧 Environment Setup -**Critical `.env` variables**: -- `MONGODB_URI` - Database connection -- `SESSION_SECRET`, `JWT_SECRET` - Security keys -- `ADMIN_EMAIL`, `ADMIN_PASSWORD` - Initial admin account -- `TELEGRAM_BOT_TOKEN` - Optional Telegram integration -- `NODE_ENV` - Controls error verbosity and security settings - -## Frontend Architecture - -### 🎨 View Layer (EJS Templates) -- **Layout system**: `views/layout.ejs` is main wrapper -- **Partials**: `views/partials/` for reusable components (navigation, footer) -- **Admin panel**: Separate layout `admin/layout.ejs` with different styling -- **Internationalization**: Content in Korean, supports locales in `/locales/` - -### 📱 PWA Implementation -- **Service Worker**: `public/sw.js` - caches static files, dynamic routes, and API responses -- **Manifest**: `public/manifest.json` - app metadata and icons -- **Offline-first**: Service worker handles network failures gracefully -- **Cache strategy**: Static files cached immediately, dynamic content cached on demand - -### 🎯 Frontend JavaScript Patterns -- **Main script**: `public/js/main.js` - handles navigation, scroll effects, form interactions -- **Calculator**: `public/js/calculator.js` - complex service pricing calculations -- **Libraries**: AOS animations, Tailwind CSS, Socket.io for real-time features - -## Key Integration Points - -### 📧 Communication Systems -- **Telegram Bot**: Optional integration for admin notifications (contact forms, orders) -- **Email**: Nodemailer for SMTP email sending (contact forms, notifications) -- **Real-time**: Socket.io setup for live updates (dashboard, notifications) - -### 🔒 Security Patterns -- **Helmet**: CSP headers allowing specific domains (fonts.googleapis.com, cdnjs.cloudflare.com) -- **Rate limiting**: Applied to `/api/*` routes (100 requests/15min per IP) -- **Input validation**: Korean-language error messages, comprehensive field validation -- **File uploads**: Multer with Sharp image processing, stored in `public/uploads/` - -### 🗄️ Database Patterns -- **Connection**: Single MongoDB connection with connection pooling -- **Indexing**: Text search on Portfolio, compound indexes for performance -- **Session storage**: MongoDB session store for persistence - -## Business Logic Specifics - -### 💰 Service Calculator -- **Complex pricing**: Base price + features + timeline modifiers -- **Multi-step form**: Service selection → customization → contact info → quote -- **Integration**: Calculator data feeds into Contact model for lead tracking - -### 🎨 Portfolio Management -- **Image handling**: Multiple images per project, primary image logic -- **Categories**: Predefined categories (web-development, mobile-app, ui-ux-design, etc.) -- **SEO optimization**: Meta fields, structured data for portfolio items - -### 👑 Admin Panel Features -- **Dashboard**: Statistics, recent activity, quick actions -- **Content management**: CRUD for Portfolio, Services, Site Settings -- **Media gallery**: File upload with image optimization -- **Contact management**: Lead tracking, status updates, response handling - -## Common Tasks & Patterns - -### 🔧 Adding New Features -1. **Model**: Create in `/models/` with proper validation and indexes -2. **Routes**: Add to `/routes/` with appropriate auth middleware -3. **Views**: EJS templates in `/views/` using layout system -4. **API**: JSON endpoints with validation middleware -5. **Frontend**: Add to `public/js/` with proper event handling - -### 🐛 Debugging Workflow -- **Development**: Use `npm run dev` for auto-restart and detailed logging -- **Database**: Check MongoDB connection and sample data with `npm run init-db` -- **Authentication**: Test both JWT (API) and session (web) auth flows -- **PWA**: Check service worker registration and cache behavior in dev tools - -### 📦 Deployment Considerations -- **Environment**: Production requires secure cookies, CSP, and rate limiting -- **Database**: Ensure MongoDB indexes are created for performance -- **Assets**: Static files served by Express, consider CDN for production -- **Monitoring**: Error handling sends different messages based on NODE_ENV - -## File Structure Quick Reference -``` -├── models/ # MongoDB schemas with business logic -├── routes/ # Express routes (API + web pages) -├── middleware/ # Auth, validation, error handling -├── views/ # EJS templates with Korean content -├── public/ # Static assets + PWA files -├── scripts/ # Database init, dev server, build tools -└── server.js # Main Express application -``` - -## Korean Language Notes -- **UI Text**: All user-facing content in Korean -- **Error Messages**: Validation errors in Korean (`middleware/validation.js`) -- **Admin Interface**: Korean labels and messages throughout admin panel -- **SEO Content**: Korean meta descriptions and structured data \ No newline at end of file diff --git a/.history/.github/copilot-instructions_20251019201424.md b/.history/.github/copilot-instructions_20251019201424.md deleted file mode 100644 index c35af6b..0000000 --- a/.history/.github/copilot-instructions_20251019201424.md +++ /dev/null @@ -1,151 +0,0 @@ -# SmartSolTech Website - AI Coding Agent Instructions - -## Project Overview -SmartSolTech is a **Korean tech services company** PWA with an admin panel, portfolio management, Telegram integration, and service calculator. Built with Node.js/Express backend, EJS templating, MongoDB, and modern PWA features. - -## Architecture & Key Patterns - -### 🏗️ Backend Architecture -- **Main server**: `server.js` - Express app with comprehensive middleware stack -- **Models**: MongoDB with Mongoose ODM - all in `/models/` (User, Portfolio, Service, Contact, SiteSettings) -- **Routes**: RESTful API patterns in `/routes/` - separate files per domain -- **Authentication**: Dual auth system - JWT tokens for API, sessions for web pages -- **Validation**: express-validator middleware in `/middleware/validation.js` with Korean error messages - -### 🔑 Authentication Pattern -```javascript -// Two authentication strategies: -// 1. JWT for API endpoints (authenticateToken) -// 2. Session-based for web pages (authenticateSession) -// See middleware/auth.js for implementations -``` - -### 📊 Data Models Key Features -- **Portfolio**: Has virtual `primaryImage` getter, text search indexes, and SEO fields -- **Service**: Complex pricing structure with features array (included/not included) -- **User**: bcrypt password hashing with pre-save hook and comparePassword method -- **All models**: Use timestamps and have soft-delete patterns where applicable - -### 🛣️ Route Organization -- **Admin routes** (`/admin`): Session-based auth, server-side rendered EJS views -- **API routes** (`/api/*`): JWT auth, JSON responses, rate-limited -- **Public routes** (`/`): Mixed auth (optional), EJS templates with PWA support - -## Development Workflow - -### 🚀 Essential Commands -```bash -# Development with hot reload and file watching -npm run dev - -# Initialize database with admin user and sample data -npm run init-db - -# Production build with webpack optimization -npm run build - -# Database setup creates: -# - Admin user (from .env: ADMIN_EMAIL/ADMIN_PASSWORD) -# - Sample services, portfolio items, site settings -``` - -### 🔧 Environment Setup -**Critical `.env` variables**: -- `MONGODB_URI` - Database connection -- `SESSION_SECRET`, `JWT_SECRET` - Security keys -- `ADMIN_EMAIL`, `ADMIN_PASSWORD` - Initial admin account -- `TELEGRAM_BOT_TOKEN` - Optional Telegram integration -- `NODE_ENV` - Controls error verbosity and security settings - -## Frontend Architecture - -### 🎨 View Layer (EJS Templates) -- **Layout system**: `views/layout.ejs` is main wrapper -- **Partials**: `views/partials/` for reusable components (navigation, footer) -- **Admin panel**: Separate layout `admin/layout.ejs` with different styling -- **Internationalization**: Content in Korean, supports locales in `/locales/` - -### 📱 PWA Implementation -- **Service Worker**: `public/sw.js` - caches static files, dynamic routes, and API responses -- **Manifest**: `public/manifest.json` - app metadata and icons -- **Offline-first**: Service worker handles network failures gracefully -- **Cache strategy**: Static files cached immediately, dynamic content cached on demand - -### 🎯 Frontend JavaScript Patterns -- **Main script**: `public/js/main.js` - handles navigation, scroll effects, form interactions -- **Calculator**: `public/js/calculator.js` - complex service pricing calculations -- **Libraries**: AOS animations, Tailwind CSS, Socket.io for real-time features - -## Key Integration Points - -### 📧 Communication Systems -- **Telegram Bot**: Optional integration for admin notifications (contact forms, orders) -- **Email**: Nodemailer for SMTP email sending (contact forms, notifications) -- **Real-time**: Socket.io setup for live updates (dashboard, notifications) - -### 🔒 Security Patterns -- **Helmet**: CSP headers allowing specific domains (fonts.googleapis.com, cdnjs.cloudflare.com) -- **Rate limiting**: Applied to `/api/*` routes (100 requests/15min per IP) -- **Input validation**: Korean-language error messages, comprehensive field validation -- **File uploads**: Multer with Sharp image processing, stored in `public/uploads/` - -### 🗄️ Database Patterns -- **Connection**: Single MongoDB connection with connection pooling -- **Indexing**: Text search on Portfolio, compound indexes for performance -- **Session storage**: MongoDB session store for persistence - -## Business Logic Specifics - -### 💰 Service Calculator -- **Complex pricing**: Base price + features + timeline modifiers -- **Multi-step form**: Service selection → customization → contact info → quote -- **Integration**: Calculator data feeds into Contact model for lead tracking - -### 🎨 Portfolio Management -- **Image handling**: Multiple images per project, primary image logic -- **Categories**: Predefined categories (web-development, mobile-app, ui-ux-design, etc.) -- **SEO optimization**: Meta fields, structured data for portfolio items - -### 👑 Admin Panel Features -- **Dashboard**: Statistics, recent activity, quick actions -- **Content management**: CRUD for Portfolio, Services, Site Settings -- **Media gallery**: File upload with image optimization -- **Contact management**: Lead tracking, status updates, response handling - -## Common Tasks & Patterns - -### 🔧 Adding New Features -1. **Model**: Create in `/models/` with proper validation and indexes -2. **Routes**: Add to `/routes/` with appropriate auth middleware -3. **Views**: EJS templates in `/views/` using layout system -4. **API**: JSON endpoints with validation middleware -5. **Frontend**: Add to `public/js/` with proper event handling - -### 🐛 Debugging Workflow -- **Development**: Use `npm run dev` for auto-restart and detailed logging -- **Database**: Check MongoDB connection and sample data with `npm run init-db` -- **Authentication**: Test both JWT (API) and session (web) auth flows -- **PWA**: Check service worker registration and cache behavior in dev tools - -### 📦 Deployment Considerations -- **Environment**: Production requires secure cookies, CSP, and rate limiting -- **Database**: Ensure MongoDB indexes are created for performance -- **Assets**: Static files served by Express, consider CDN for production -- **Monitoring**: Error handling sends different messages based on NODE_ENV - -## File Structure Quick Reference -``` -├── models/ # MongoDB schemas with business logic -├── routes/ # Express routes (API + web pages) -├── middleware/ # Auth, validation, error handling -├── views/ # EJS templates with Korean content -├── public/ # Static assets + PWA files -├── scripts/ # Database init, dev server, build tools -└── server.js # Main Express application -``` - -## Korean Language Notes -- **UI Text**: All user-facing content in Korean -- **Error Messages**: Validation errors in Korean (`middleware/validation.js`) -- **Admin Interface**: Korean labels and messages throughout admin panel -- **SEO Content**: Korean meta descriptions and structured data \ No newline at end of file diff --git a/.history/.gitignore_20251019160509 b/.history/.gitignore_20251019160509 deleted file mode 100644 index 239c253..0000000 --- a/.history/.gitignore_20251019160509 +++ /dev/null @@ -1,20 +0,0 @@ -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/ \ No newline at end of file diff --git a/.history/.gitignore_20251019162544 b/.history/.gitignore_20251019162544 deleted file mode 100644 index 239c253..0000000 --- a/.history/.gitignore_20251019162544 +++ /dev/null @@ -1,20 +0,0 @@ -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/ \ No newline at end of file diff --git a/.history/README_20251019162701.md b/.history/README_20251019162701.md deleted file mode 100644 index e831520..0000000 --- a/.history/README_20251019162701.md +++ /dev/null @@ -1,280 +0,0 @@ -# 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 -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** - Умные решения для вашего бизнеса 🚀 \ No newline at end of file diff --git a/.history/README_20251019163806.md b/.history/README_20251019163806.md deleted file mode 100644 index e831520..0000000 --- a/.history/README_20251019163806.md +++ /dev/null @@ -1,280 +0,0 @@ -# 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 -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** - Умные решения для вашего бизнеса 🚀 \ No newline at end of file diff --git a/.history/REPORT_20251020225831.md b/.history/REPORT_20251020225831.md deleted file mode 100644 index 584e50e..0000000 --- a/.history/REPORT_20251020225831.md +++ /dev/null @@ -1,132 +0,0 @@ -# 🎯 SmartSolTech - Отчет о Выполненных Исправлениях - -## 📋 Задачи которые были выполнены: - -### 1. 🏠 **Исправление главной страницы** -- ✅ **Проблема**: Стили на главной странице были поломаны -- ✅ **Решение**: - - Полностью переписан `base.css` с принудительными стилями (!important) - - Улучшен градиент hero-секции с многослойными эффектами - - Добавлены анимации и hover-эффекты для кнопок - - Исправлена навигация с backdrop-filter и webkit префиксами - - Оптимизирована отзывчивость для мобильных устройств - -### 2. 📐 **Компактные баннеры для внутренних страниц** -- ✅ **Проблема**: Hero-баннеры на всех страницах были полноэкранными -- ✅ **Решение**: - - Создан класс `.hero-section-compact` для внутренних страниц - - Уменьшена высота с 100vh до 40vh (50vh максимум) - - Обновлены страницы: "О нас", "Услуги" - - Сохранен полноэкранный баннер только на главной странице - -### 3. 🖼️ **Система редактирования изображений баннеров** - -#### **A. Backend API (/routes/media.js)** -- ✅ Загрузка одного изображения: `POST /media/upload` -- ✅ Загрузка нескольких изображений: `POST /media/upload-multiple` -- ✅ Удаление изображений: `DELETE /media/:filename` -- ✅ Список изображений: `GET /media/list` -- ✅ Автоматическая оптимизация изображений с Sharp -- ✅ Создание thumbnails (300x200, 800x600, 1200x900) -- ✅ Конвертация в WebP для лучшего сжатия -- ✅ Безопасность: аутентификация и валидация файлов - -#### **B. Frontend Редактор (/views/admin/banner-editor.ejs)** -- ✅ Интуитивный интерфейс с табами для разных страниц -- ✅ Drag & Drop загрузка изображений -- ✅ Превью изображений с возможностью удаления -- ✅ Индикатор прогресса загрузки -- ✅ Галерея загруженных изображений -- ✅ Мгновенная смена баннеров одним кликом -- ✅ Локальное сохранение настроек в localStorage - -#### **C. Интеграция с админкой** -- ✅ Новый маршрут: `/admin/banner-editor` -- ✅ Аутентификация через админ-сессии -- ✅ Интеграция с существующей админ панелью - -## 🔧 Технические улучшения: - -### **CSS архитектура:** -```css -base.css (16KB) - Принудительные стили и reset -main.css (11KB) - Компоненты и анимации + компактные баннеры -fixes.css (6KB) - Дополнительные исправления -``` - -### **Оптимизация изображений:** -- Автоматическое создание 4 размеров (thumbnail, medium, large, original) -- Конвертация в WebP (экономия до 50% размера) -- Максимальный размер файла: 10MB -- Поддержка: JPG, PNG, GIF, WebP - -### **Безопасность:** -- Валидация типов файлов -- Защита от path traversal атак -- Аутентификация для всех операций -- Автоматическая очистка при ошибках - -## 📊 Результаты тестирования: - -``` -🏠 ГЛАВНАЯ СТРАНИЦА: ✅ 200 OK (0.015s) -🎨 О НАС (компактный): ✅ 200 OK (0.007s) -🎨 УСЛУГИ (компактный): ✅ 200 OK (0.009s) -📱 ПОРТФОЛИО: ✅ 200 OK (0.012s) -🧮 КАЛЬКУЛЯТОР: ✅ 200 OK (0.008s) -📸 РЕДАКТОР БАННЕРОВ: ✅ 302 (требует авторизации) -🎨 CSS ФАЙЛЫ: ✅ Все загружаются корректно -📁 UPLOADS ПАПКА: ✅ Создана и готова -``` - -## 🚀 Как использовать редактор баннеров: - -### **Шаг 1: Доступ** -``` -URL: http://localhost:3000/admin/banner-editor -Требуется: Авторизация в админ панели -``` - -### **Шаг 2: Загрузка изображений** -1. Нажать "Загрузить Изображения" -2. Перетащить файлы или выбрать через кнопку -3. Посмотреть превью и нажать "Загрузить" -4. Система автоматически создаст оптимизированные версии - -### **Шаг 3: Установка баннера** -1. Выбрать страницу во вкладках (Главная, О нас, Услуги, Портфолио) -2. Нажать "Использовать" на нужном изображении -3. Баннер мгновенно обновится -4. Настройки сохраняются автоматически - -### **Шаг 4: Управление** -- Удаление: кнопка "Удалить" в галерее -- Обновление: кнопка "Обновить" -- Сброс к градиенту: кнопка "Удалить" на текущем баннере - -## 🎯 Итоговое состояние: - -### ✅ **Что работает:** -- Главная страница с исправленными стилями -- Компактные баннеры на внутренних страницах -- Полнофункциональный редактор изображений -- API для загрузки и управления медиа -- Автоматическая оптимизация изображений -- Безопасная система аутентификации - -### 🔄 **Готово к использованию:** -- Сервер: `http://localhost:3000` (PID: 24059) -- Админка: `http://localhost:3000/admin` -- Редактор: `http://localhost:3000/admin/banner-editor` -- Папка загрузок: `/public/uploads/` (готова к использованию) - -### 📈 **Преимущества реализации:** -1. **Производительность**: WebP формат + множественные размеры -2. **UX**: Drag & Drop + мгновенные превью -3. **Безопасность**: Полная валидация + аутентификация -4. **Масштабируемость**: Готова к добавлению новых страниц -5. **Мобильность**: Отзывчивый дизайн на всех устройствах - ---- - -**🎉 Все задачи выполнены успешно! Система готова к продуктивному использованию.** \ No newline at end of file diff --git a/.history/REPORT_20251020225845.md b/.history/REPORT_20251020225845.md deleted file mode 100644 index 584e50e..0000000 --- a/.history/REPORT_20251020225845.md +++ /dev/null @@ -1,132 +0,0 @@ -# 🎯 SmartSolTech - Отчет о Выполненных Исправлениях - -## 📋 Задачи которые были выполнены: - -### 1. 🏠 **Исправление главной страницы** -- ✅ **Проблема**: Стили на главной странице были поломаны -- ✅ **Решение**: - - Полностью переписан `base.css` с принудительными стилями (!important) - - Улучшен градиент hero-секции с многослойными эффектами - - Добавлены анимации и hover-эффекты для кнопок - - Исправлена навигация с backdrop-filter и webkit префиксами - - Оптимизирована отзывчивость для мобильных устройств - -### 2. 📐 **Компактные баннеры для внутренних страниц** -- ✅ **Проблема**: Hero-баннеры на всех страницах были полноэкранными -- ✅ **Решение**: - - Создан класс `.hero-section-compact` для внутренних страниц - - Уменьшена высота с 100vh до 40vh (50vh максимум) - - Обновлены страницы: "О нас", "Услуги" - - Сохранен полноэкранный баннер только на главной странице - -### 3. 🖼️ **Система редактирования изображений баннеров** - -#### **A. Backend API (/routes/media.js)** -- ✅ Загрузка одного изображения: `POST /media/upload` -- ✅ Загрузка нескольких изображений: `POST /media/upload-multiple` -- ✅ Удаление изображений: `DELETE /media/:filename` -- ✅ Список изображений: `GET /media/list` -- ✅ Автоматическая оптимизация изображений с Sharp -- ✅ Создание thumbnails (300x200, 800x600, 1200x900) -- ✅ Конвертация в WebP для лучшего сжатия -- ✅ Безопасность: аутентификация и валидация файлов - -#### **B. Frontend Редактор (/views/admin/banner-editor.ejs)** -- ✅ Интуитивный интерфейс с табами для разных страниц -- ✅ Drag & Drop загрузка изображений -- ✅ Превью изображений с возможностью удаления -- ✅ Индикатор прогресса загрузки -- ✅ Галерея загруженных изображений -- ✅ Мгновенная смена баннеров одним кликом -- ✅ Локальное сохранение настроек в localStorage - -#### **C. Интеграция с админкой** -- ✅ Новый маршрут: `/admin/banner-editor` -- ✅ Аутентификация через админ-сессии -- ✅ Интеграция с существующей админ панелью - -## 🔧 Технические улучшения: - -### **CSS архитектура:** -```css -base.css (16KB) - Принудительные стили и reset -main.css (11KB) - Компоненты и анимации + компактные баннеры -fixes.css (6KB) - Дополнительные исправления -``` - -### **Оптимизация изображений:** -- Автоматическое создание 4 размеров (thumbnail, medium, large, original) -- Конвертация в WebP (экономия до 50% размера) -- Максимальный размер файла: 10MB -- Поддержка: JPG, PNG, GIF, WebP - -### **Безопасность:** -- Валидация типов файлов -- Защита от path traversal атак -- Аутентификация для всех операций -- Автоматическая очистка при ошибках - -## 📊 Результаты тестирования: - -``` -🏠 ГЛАВНАЯ СТРАНИЦА: ✅ 200 OK (0.015s) -🎨 О НАС (компактный): ✅ 200 OK (0.007s) -🎨 УСЛУГИ (компактный): ✅ 200 OK (0.009s) -📱 ПОРТФОЛИО: ✅ 200 OK (0.012s) -🧮 КАЛЬКУЛЯТОР: ✅ 200 OK (0.008s) -📸 РЕДАКТОР БАННЕРОВ: ✅ 302 (требует авторизации) -🎨 CSS ФАЙЛЫ: ✅ Все загружаются корректно -📁 UPLOADS ПАПКА: ✅ Создана и готова -``` - -## 🚀 Как использовать редактор баннеров: - -### **Шаг 1: Доступ** -``` -URL: http://localhost:3000/admin/banner-editor -Требуется: Авторизация в админ панели -``` - -### **Шаг 2: Загрузка изображений** -1. Нажать "Загрузить Изображения" -2. Перетащить файлы или выбрать через кнопку -3. Посмотреть превью и нажать "Загрузить" -4. Система автоматически создаст оптимизированные версии - -### **Шаг 3: Установка баннера** -1. Выбрать страницу во вкладках (Главная, О нас, Услуги, Портфолио) -2. Нажать "Использовать" на нужном изображении -3. Баннер мгновенно обновится -4. Настройки сохраняются автоматически - -### **Шаг 4: Управление** -- Удаление: кнопка "Удалить" в галерее -- Обновление: кнопка "Обновить" -- Сброс к градиенту: кнопка "Удалить" на текущем баннере - -## 🎯 Итоговое состояние: - -### ✅ **Что работает:** -- Главная страница с исправленными стилями -- Компактные баннеры на внутренних страницах -- Полнофункциональный редактор изображений -- API для загрузки и управления медиа -- Автоматическая оптимизация изображений -- Безопасная система аутентификации - -### 🔄 **Готово к использованию:** -- Сервер: `http://localhost:3000` (PID: 24059) -- Админка: `http://localhost:3000/admin` -- Редактор: `http://localhost:3000/admin/banner-editor` -- Папка загрузок: `/public/uploads/` (готова к использованию) - -### 📈 **Преимущества реализации:** -1. **Производительность**: WebP формат + множественные размеры -2. **UX**: Drag & Drop + мгновенные превью -3. **Безопасность**: Полная валидация + аутентификация -4. **Масштабируемость**: Готова к добавлению новых страниц -5. **Мобильность**: Отзывчивый дизайн на всех устройствах - ---- - -**🎉 Все задачи выполнены успешно! Система готова к продуктивному использованию.** \ No newline at end of file diff --git a/.history/config/database_20251019201726.js b/.history/config/database_20251019201726.js deleted file mode 100644 index 10b6a8f..0000000 --- a/.history/config/database_20251019201726.js +++ /dev/null @@ -1,30 +0,0 @@ -const { Sequelize } = require('sequelize'); -require('dotenv').config(); - -const sequelize = new Sequelize(process.env.DATABASE_URL || { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, - database: process.env.DB_NAME || 'smartsoltech', - username: process.env.DB_USER || 'postgres', - password: process.env.DB_PASSWORD || 'password', - dialect: 'postgres', - logging: process.env.NODE_ENV === 'development' ? console.log : false, - pool: { - max: 5, - min: 0, - acquire: 30000, - idle: 10000 - } -}); - -// Test the connection -async function testConnection() { - try { - await sequelize.authenticate(); - console.log('✓ PostgreSQL connected successfully'); - } catch (error) { - console.error('✗ PostgreSQL connection error:', error); - } -} - -module.exports = { sequelize, testConnection }; \ No newline at end of file diff --git a/.history/config/database_20251019201735.js b/.history/config/database_20251019201735.js deleted file mode 100644 index 10b6a8f..0000000 --- a/.history/config/database_20251019201735.js +++ /dev/null @@ -1,30 +0,0 @@ -const { Sequelize } = require('sequelize'); -require('dotenv').config(); - -const sequelize = new Sequelize(process.env.DATABASE_URL || { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, - database: process.env.DB_NAME || 'smartsoltech', - username: process.env.DB_USER || 'postgres', - password: process.env.DB_PASSWORD || 'password', - dialect: 'postgres', - logging: process.env.NODE_ENV === 'development' ? console.log : false, - pool: { - max: 5, - min: 0, - acquire: 30000, - idle: 10000 - } -}); - -// Test the connection -async function testConnection() { - try { - await sequelize.authenticate(); - console.log('✓ PostgreSQL connected successfully'); - } catch (error) { - console.error('✗ PostgreSQL connection error:', error); - } -} - -module.exports = { sequelize, testConnection }; \ No newline at end of file diff --git a/.history/locales/en_20251019171415.json b/.history/locales/en_20251019171415.json deleted file mode 100644 index 36e700e..0000000 --- a/.history/locales/en_20251019171415.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251019171645.json b/.history/locales/en_20251019171645.json deleted file mode 100644 index 36e700e..0000000 --- a/.history/locales/en_20251019171645.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251019181515.json b/.history/locales/en_20251019181515.json deleted file mode 100644 index d0209de..0000000 --- a/.history/locales/en_20251019181515.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251019181629.json b/.history/locales/en_20251019181629.json deleted file mode 100644 index d0209de..0000000 --- a/.history/locales/en_20251019181629.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021183321.json b/.history/locales/en_20251021183321.json deleted file mode 100644 index 21143ff..0000000 --- a/.history/locales/en_20251021183321.json +++ /dev/null @@ -1,383 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "hero.title.smart", - "solutions": "hero.title.solutions" - }, - "subtitle": "Solutions", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "portfolio.subtitle" - }, - "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", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "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" - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021183333.json b/.history/locales/en_20251021183333.json deleted file mode 100644 index 21143ff..0000000 --- a/.history/locales/en_20251021183333.json +++ /dev/null @@ -1,383 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "hero.title.smart", - "solutions": "hero.title.solutions" - }, - "subtitle": "Solutions", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "portfolio.subtitle" - }, - "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", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "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" - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021184946.json b/.history/locales/en_20251021184946.json deleted file mode 100644 index 733cff5..0000000 --- a/.history/locales/en_20251021184946.json +++ /dev/null @@ -1,383 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "portfolio.subtitle" - }, - "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", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "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" - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185005.json b/.history/locales/en_20251021185005.json deleted file mode 100644 index 64f6e28..0000000 --- a/.history/locales/en_20251021185005.json +++ /dev/null @@ -1,410 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "portfolio.subtitle" - }, - "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", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "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" - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185016.json b/.history/locales/en_20251021185016.json deleted file mode 100644 index 98a74f6..0000000 --- a/.history/locales/en_20251021185016.json +++ /dev/null @@ -1,431 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "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" - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185033.json b/.history/locales/en_20251021185033.json deleted file mode 100644 index 266ba3a..0000000 --- a/.history/locales/en_20251021185033.json +++ /dev/null @@ -1,435 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": { - "description": "footer.company.description" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "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" - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185044.json b/.history/locales/en_20251021185044.json deleted file mode 100644 index fc3bd8c..0000000 --- a/.history/locales/en_20251021185044.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185051.json b/.history/locales/en_20251021185051.json deleted file mode 100644 index 3983732..0000000 --- a/.history/locales/en_20251021185051.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185058.json b/.history/locales/en_20251021185058.json deleted file mode 100644 index 653a3d9..0000000 --- a/.history/locales/en_20251021185058.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185105.json b/.history/locales/en_20251021185105.json deleted file mode 100644 index 7e51b81..0000000 --- a/.history/locales/en_20251021185105.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185114.json b/.history/locales/en_20251021185114.json deleted file mode 100644 index cbf7b8c..0000000 --- a/.history/locales/en_20251021185114.json +++ /dev/null @@ -1,449 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021185126.json b/.history/locales/en_20251021185126.json deleted file mode 100644 index 250e393..0000000 --- a/.history/locales/en_20251021185126.json +++ /dev/null @@ -1,473 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": "We'll Grow Together", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021190951.json b/.history/locales/en_20251021190951.json deleted file mode 100644 index 250e393..0000000 --- a/.history/locales/en_20251021190951.json +++ /dev/null @@ -1,473 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": "We'll Grow Together", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021205934.json b/.history/locales/en_20251021205934.json deleted file mode 100644 index 1b9134d..0000000 --- a/.history/locales/en_20251021205934.json +++ /dev/null @@ -1,477 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": "We'll Grow Together", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021205945.json b/.history/locales/en_20251021205945.json deleted file mode 100644 index 642345f..0000000 --- a/.history/locales/en_20251021205945.json +++ /dev/null @@ -1,478 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or 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": "We'll Grow Together", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021210243.json b/.history/locales/en_20251021210243.json deleted file mode 100644 index 2505905..0000000 --- a/.history/locales/en_20251021210243.json +++ /dev/null @@ -1,483 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678", - "hours": "Mon-Fri 9:00 - 18:00" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr", - "response": "We respond within 24 hours" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "address": { - "line1": "Office Address", - "line2": "123 Teheran-ro, Gangnam-gu" - }, - "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": "We'll Grow Together", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021210341.json b/.history/locales/en_20251021210341.json deleted file mode 100644 index 2505905..0000000 --- a/.history/locales/en_20251021210341.json +++ /dev/null @@ -1,483 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "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", - "subtitle": "Professional development services to turn your ideas into reality", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "subtitle": "Check out successfully completed projects", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "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", - "title": "Project Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - }, - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678", - "hours": "Mon-Fri 9:00 - 18:00" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr", - "response": "We respond within 24 hours" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "address": { - "line1": "Office Address", - "line2": "123 Teheran-ro, Gangnam-gu" - }, - "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": "We'll Grow Together", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "Grow your business with innovative technology" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "Quick Links", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. All rights reserved.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021210347.json b/.history/locales/en_20251021210347.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.history/locales/en_20251021210347.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/.history/locales/en_20251021210512.json b/.history/locales/en_20251021210512.json deleted file mode 100644 index 52e83eb..0000000 --- a/.history/locales/en_20251021210512.json +++ /dev/null @@ -1,286 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "subtitle": "Professional development services to turn your ideas into reality", - "description": "Digital solutions completed with cutting-edge technology and creative ideas", - "view_all": "View All Services", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects" - }, - "subtitle": "Check out successfully completed projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" - } - }, - "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" - } - }, - "contact": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "ready_title": "Ready to Start Your Project?", - "ready_description": "Turn your ideas into reality. Experts provide the best solutions.", - "form": { - "title": "Project Inquiry", - "name": "Name", - "email": "Email", - "phone": "Phone", - "message": "Message", - "submit": "Send Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - } - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Innovation", - "description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology." - }, - "quality": { - "title": "Quality", - "description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with." - }, - "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." - } - }, - "cta": { - "title": "We'll Grow Together", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "Quick Links" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. 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_details": "View Details" - }, - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin" - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678" - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021210629.json b/.history/locales/en_20251021210629.json deleted file mode 100644 index 52e83eb..0000000 --- a/.history/locales/en_20251021210629.json +++ /dev/null @@ -1,286 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "subtitle": "Professional development services to turn your ideas into reality", - "description": "Digital solutions completed with cutting-edge technology and creative ideas", - "view_all": "View All Services", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects" - }, - "subtitle": "Check out successfully completed projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" - } - }, - "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" - } - }, - "contact": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "ready_title": "Ready to Start Your Project?", - "ready_description": "Turn your ideas into reality. Experts provide the best solutions.", - "form": { - "title": "Project Inquiry", - "name": "Name", - "email": "Email", - "phone": "Phone", - "message": "Message", - "submit": "Send Inquiry", - "service": { - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - } - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Innovation", - "description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology." - }, - "quality": { - "title": "Quality", - "description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with." - }, - "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." - } - }, - "cta": { - "title": "We'll Grow Together", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "Quick Links" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. 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_details": "View Details" - }, - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin" - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678" - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021210647.json b/.history/locales/en_20251021210647.json deleted file mode 100644 index 97451a5..0000000 --- a/.history/locales/en_20251021210647.json +++ /dev/null @@ -1,292 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "subtitle": "Professional development services to turn your ideas into reality", - "description": "Digital solutions completed with cutting-edge technology and creative ideas", - "view_all": "View All Services", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects" - }, - "subtitle": "Check out successfully completed projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" - } - }, - "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" - } - }, - "contact": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "ready_title": "Ready to Start Your Project?", - "ready_description": "Turn your ideas into reality. Experts provide the best solutions.", - "form": { - "title": "Project Inquiry", - "name": "Name", - "email": "Email", - "phone": "Phone", - "message": "Message", - "submit": "Send Inquiry", - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry", - "service": { - "title": "Service Interest", - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - } - }, - "info": { - "title": "Contact Information" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Innovation", - "description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology." - }, - "quality": { - "title": "Quality", - "description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with." - }, - "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." - } - }, - "cta": { - "title": "We'll Grow Together", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "Quick Links" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. 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_details": "View Details" - }, - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin" - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678" - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021210656.json b/.history/locales/en_20251021210656.json deleted file mode 100644 index 6ad919a..0000000 --- a/.history/locales/en_20251021210656.json +++ /dev/null @@ -1,299 +0,0 @@ -{ - "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin" - }, - "hero": { - "title": { - "smart": "Smart", - "solutions": "Solutions" - }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" - } - }, - "services": { - "title": { - "our": "Our", - "services": "Services" - }, - "subtitle": "Professional development services to turn your ideas into reality", - "description": "Digital solutions completed with cutting-edge technology and creative ideas", - "view_all": "View All Services", - "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" - }, - "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" - }, - "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" - }, - "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" - } - } - }, - "portfolio": { - "title": { - "recent": "Recent", - "projects": "Projects" - }, - "subtitle": "Check out successfully completed projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "project_details": "Project Details", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" - } - }, - "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" - } - }, - "contact": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "ready_title": "Ready to Start Your Project?", - "ready_description": "Turn your ideas into reality. Experts provide the best solutions.", - "form": { - "title": "Project Inquiry", - "name": "Name", - "email": "Email", - "phone": "Phone", - "message": "Message", - "submit": "Send Inquiry", - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry", - "service": { - "title": "Service Interest", - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - } - }, - "info": { - "title": "Contact Information" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678", - "hours": "Mon-Fri 9:00-18:00" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr", - "response": "Response within 24 hours" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "address": { - "title": "Office Address", - "line1": "123 Teheran-ro, Gangnam-gu", - "line2": "Seoul, South Korea" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Innovation", - "description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology." - }, - "quality": { - "title": "Quality", - "description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with." - }, - "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." - } - }, - "cta": { - "title": "We'll Grow Together", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "Quick Links" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. 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_details": "View Details" - }, - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin" - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678" - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/en_20251021211513.json b/.history/locales/en_20251025214900.json similarity index 72% rename from .history/locales/en_20251021211513.json rename to .history/locales/en_20251025214900.json index 64bfc8b..a807050 100644 --- a/.history/locales/en_20251021211513.json +++ b/.history/locales/en_20251025214900.json @@ -70,10 +70,52 @@ "process": { "title": "Project Implementation Process", "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { + "step1": { "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" + "description": "Accurately understand customer requirements and plan optimal solutions" + }, + "step2": { + "title": "Design and Architecture", + "description": "Design user-centered intuitive design and robust system architecture" + }, + "step3": { + "title": "Development and Implementation", + "description": "Develop efficient and scalable solutions using the latest technology and best practices" + }, + "step4": { + "title": "Testing and Deployment", + "description": "Ensure quality through thorough testing and proceed with stable deployment" } + }, + "why_choose": { + "title": "Why Choose SmartSolTech?", + "modern_tech": { + "title": "Modern Technology Utilization", + "description": "Always keep up with the latest technology trends and use proven technology stacks to provide future-oriented solutions." + }, + "expert_team": { + "title": "Expert Team", + "description": "A team of experts in each field collaborates to guarantee the highest quality results." + }, + "fast_response": { + "title": "Fast Response", + "description": "Complete projects within scheduled timeframes through quick communication and efficient project management." + }, + "continuous_support": { + "title": "Continuous Support", + "description": "Maintain long-term partnerships through continuous maintenance and technical support even after project completion." + }, + "quality_guarantee": { + "title": "Quality Guarantee", + "subtitle": "For customer satisfaction\nHighest quality service" + } + }, + "cta": { + "title": "Ready to Start Your Project?", + "subtitle": "We'll propose the optimal solution through free consultation", + "free_consultation": "Apply for Free Consultation", + "calculate_cost": "Calculate Cost", + "view_portfolio": "View Portfolio" } }, "portfolio": { @@ -103,9 +145,46 @@ "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" } }, + "portfolio_page": { + "title": "Our Portfolio", + "subtitle": "Discover innovative projects and creative solutions", + "categories": { + "all": "All", + "web-development": "Web Development", + "mobile-app": "Mobile App", + "ui-ux-design": "UI/UX Design", + "branding": "Branding", + "marketing": "Digital Marketing" + }, + "buttons": { + "details": "View Details", + "projectDetails": "Project Details", + "loadMore": "Load More Projects", + "contact": "Request Project", + "calculate": "Calculate Cost" + }, + "empty": { + "title": "No portfolio yet", + "subtitle": "We'll be showcasing amazing projects soon!" + }, + "cta": { + "title": "Be the star of the next project", + "subtitle": "Create innovative digital solutions with us" + }, + "labels": { + "featured": "FEATURED", + "views": "views", + "likes": "likes" + } + }, "calculator": { "title": "Project Cost Calculator", "subtitle": "Select your desired services and requirements to get accurate cost estimates in real time", + "next_step": "Next Step", + "prev_step": "Previous Step", + "calculate": "Calculate", + + "reset": "Reset", "meta": { "title": "Project Cost Calculator", "description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator" @@ -114,6 +193,48 @@ "title": "Check Your Project Estimate", "subtitle": "Select your desired services and requirements to calculate costs in real time", "button": "Use Cost Calculator" + }, + "step1": { + "title": "Choose Your Services", + "nav_title": "Step 1: Services", + "subtitle": "Select the services you need for your project" + }, + "step2": { + "title": "Project Details", + "nav_title": "Step 2: Details", + "subtitle": "Tell us about your project complexity and timeline" + }, + "complexity": { + "title": "Project Complexity", + "simple": "Simple", + "simple_desc": "Basic features and standard functionality", + "medium": "Medium", + "medium_desc": "Custom features and integrations", + "complex": "Complex", + "complex_desc": "Advanced features and complex integrations" + }, + "timeline": { + "title": "Project Timeline", + "standard": "Standard (4-8 weeks)", + "standard_desc": "Normal development timeline", + "rush": "Rush (2-4 weeks)", + "rush_desc": "Expedited delivery with additional cost", + "extended": "Extended (8+ weeks)", + "extended_desc": "Flexible timeline with cost optimization" + }, + "result": { + "title": "Project Estimate", + "nav_title": "Result", + "subtitle": "Your estimated project cost", + "estimated_price": "Estimated Price", + "price_note": "This is an approximate cost. Final price may vary based on project complexity.", + "summary": "Project Summary", + "get_quote": "Get Detailed Quote", + "contact_note": "Contact us for a detailed consultation and precise quote.", + "recalculate": "Calculate Again", + "selected_services": "Selected Services", + "complexity": "Complexity", + "timeline": "Timeline" } }, "contact": { @@ -221,6 +342,9 @@ }, "footer": { "description": "Digital solution specialist leading innovation", + "company": { + "description": "Digital solution specialist leading innovation" + }, "links": { "title": "Quick Links" }, @@ -230,7 +354,9 @@ "phone": "+82-2-1234-5678", "address": "123 Teheran-ro, Gangnam-gu, Seoul" }, - "copyright": "© 2024 SmartSolTech. All rights reserved." + "copyright": "© {{year}} SmartSolTech. All rights reserved.", + "privacy": "Privacy Policy", + "terms": "Terms of Service" }, "theme": { "light": "Light Theme", @@ -295,37 +421,5 @@ "portfolio": "Portfolio", "contact": "Contact", "calculator": "Calculator" - }, - "portfolio_page": { - "title": "Our Portfolio", - "subtitle": "Discover innovative projects and creative solutions", - "categories": { - "all": "All", - "web-development": "Web Development", - "mobile-app": "Mobile App", - "ui-ux-design": "UI/UX Design", - "branding": "Branding", - "marketing": "Digital Marketing" - }, - "buttons": { - "details": "View Details", - "projectDetails": "Project Details", - "loadMore": "Load More Projects", - "contact": "Request Project", - "calculate": "Calculate Cost" - }, - "empty": { - "title": "No portfolio yet", - "subtitle": "We'll be showcasing amazing projects soon!" - }, - "cta": { - "title": "Be the star of the next project", - "subtitle": "Create innovative digital solutions with us" - }, - "labels": { - "featured": "FEATURED", - "views": "views", - "likes": "likes" - } } } \ No newline at end of file diff --git a/.history/locales/ko_20251021211904.json b/.history/locales/en_20251025215055.json similarity index 67% rename from .history/locales/ko_20251021211904.json rename to .history/locales/en_20251025215055.json index ea46c6e..a807050 100644 --- a/.history/locales/ko_20251021211904.json +++ b/.history/locales/en_20251025215055.json @@ -70,10 +70,52 @@ "process": { "title": "Project Implementation Process", "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { + "step1": { "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" + "description": "Accurately understand customer requirements and plan optimal solutions" + }, + "step2": { + "title": "Design and Architecture", + "description": "Design user-centered intuitive design and robust system architecture" + }, + "step3": { + "title": "Development and Implementation", + "description": "Develop efficient and scalable solutions using the latest technology and best practices" + }, + "step4": { + "title": "Testing and Deployment", + "description": "Ensure quality through thorough testing and proceed with stable deployment" } + }, + "why_choose": { + "title": "Why Choose SmartSolTech?", + "modern_tech": { + "title": "Modern Technology Utilization", + "description": "Always keep up with the latest technology trends and use proven technology stacks to provide future-oriented solutions." + }, + "expert_team": { + "title": "Expert Team", + "description": "A team of experts in each field collaborates to guarantee the highest quality results." + }, + "fast_response": { + "title": "Fast Response", + "description": "Complete projects within scheduled timeframes through quick communication and efficient project management." + }, + "continuous_support": { + "title": "Continuous Support", + "description": "Maintain long-term partnerships through continuous maintenance and technical support even after project completion." + }, + "quality_guarantee": { + "title": "Quality Guarantee", + "subtitle": "For customer satisfaction\nHighest quality service" + } + }, + "cta": { + "title": "Ready to Start Your Project?", + "subtitle": "We'll propose the optimal solution through free consultation", + "free_consultation": "Apply for Free Consultation", + "calculate_cost": "Calculate Cost", + "view_portfolio": "View Portfolio" } }, "portfolio": { @@ -103,9 +145,46 @@ "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" } }, + "portfolio_page": { + "title": "Our Portfolio", + "subtitle": "Discover innovative projects and creative solutions", + "categories": { + "all": "All", + "web-development": "Web Development", + "mobile-app": "Mobile App", + "ui-ux-design": "UI/UX Design", + "branding": "Branding", + "marketing": "Digital Marketing" + }, + "buttons": { + "details": "View Details", + "projectDetails": "Project Details", + "loadMore": "Load More Projects", + "contact": "Request Project", + "calculate": "Calculate Cost" + }, + "empty": { + "title": "No portfolio yet", + "subtitle": "We'll be showcasing amazing projects soon!" + }, + "cta": { + "title": "Be the star of the next project", + "subtitle": "Create innovative digital solutions with us" + }, + "labels": { + "featured": "FEATURED", + "views": "views", + "likes": "likes" + } + }, "calculator": { "title": "Project Cost Calculator", "subtitle": "Select your desired services and requirements to get accurate cost estimates in real time", + "next_step": "Next Step", + "prev_step": "Previous Step", + "calculate": "Calculate", + + "reset": "Reset", "meta": { "title": "Project Cost Calculator", "description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator" @@ -114,6 +193,48 @@ "title": "Check Your Project Estimate", "subtitle": "Select your desired services and requirements to calculate costs in real time", "button": "Use Cost Calculator" + }, + "step1": { + "title": "Choose Your Services", + "nav_title": "Step 1: Services", + "subtitle": "Select the services you need for your project" + }, + "step2": { + "title": "Project Details", + "nav_title": "Step 2: Details", + "subtitle": "Tell us about your project complexity and timeline" + }, + "complexity": { + "title": "Project Complexity", + "simple": "Simple", + "simple_desc": "Basic features and standard functionality", + "medium": "Medium", + "medium_desc": "Custom features and integrations", + "complex": "Complex", + "complex_desc": "Advanced features and complex integrations" + }, + "timeline": { + "title": "Project Timeline", + "standard": "Standard (4-8 weeks)", + "standard_desc": "Normal development timeline", + "rush": "Rush (2-4 weeks)", + "rush_desc": "Expedited delivery with additional cost", + "extended": "Extended (8+ weeks)", + "extended_desc": "Flexible timeline with cost optimization" + }, + "result": { + "title": "Project Estimate", + "nav_title": "Result", + "subtitle": "Your estimated project cost", + "estimated_price": "Estimated Price", + "price_note": "This is an approximate cost. Final price may vary based on project complexity.", + "summary": "Project Summary", + "get_quote": "Get Detailed Quote", + "contact_note": "Contact us for a detailed consultation and precise quote.", + "recalculate": "Calculate Again", + "selected_services": "Selected Services", + "complexity": "Complexity", + "timeline": "Timeline" } }, "contact": { @@ -221,6 +342,9 @@ }, "footer": { "description": "Digital solution specialist leading innovation", + "company": { + "description": "Digital solution specialist leading innovation" + }, "links": { "title": "Quick Links" }, @@ -230,7 +354,9 @@ "phone": "+82-2-1234-5678", "address": "123 Teheran-ro, Gangnam-gu, Seoul" }, - "copyright": "© 2024 SmartSolTech. All rights reserved." + "copyright": "© {{year}} SmartSolTech. All rights reserved.", + "privacy": "Privacy Policy", + "terms": "Terms of Service" }, "theme": { "light": "Light Theme", @@ -295,37 +421,5 @@ "portfolio": "Portfolio", "contact": "Contact", "calculator": "Calculator" - }, - "portfolio_page": { - "title": "우리의 포트폴리오", - "subtitle": "혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요", - "categories": { - "all": "전체", - "web-development": "웹 개발", - "mobile-app": "모바일 앱", - "ui-ux-design": "UI/UX 디자인", - "branding": "브랜딩", - "marketing": "디지털 마케팅" - }, - "buttons": { - "details": "자세히 보기", - "projectDetails": "프로젝트 상세보기", - "loadMore": "더 많은 프로젝트 보기", - "contact": "프로젝트 문의하기", - "calculate": "비용 계산하기" - }, - "empty": { - "title": "아직 포트폴리오가 없습니다", - "subtitle": "곧 멋진 프로젝트들을 공개할 예정입니다!" - }, - "cta": { - "title": "다음 프로젝트의 주인공이 되어보세요", - "subtitle": "우리와 함께 혁신적인 디지털 솔루션을 만들어보세요" - }, - "labels": { - "featured": "추천", - "views": "조회수", - "likes": "좋아요" - } } } \ No newline at end of file diff --git a/.history/locales/kk_20251019171626.json b/.history/locales/kk_20251019171626.json deleted file mode 100644 index 632b696..0000000 --- a/.history/locales/kk_20251019171626.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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": "Алдыңғы" - } -} \ No newline at end of file diff --git a/.history/locales/kk_20251019171645.json b/.history/locales/kk_20251019171645.json deleted file mode 100644 index 632b696..0000000 --- a/.history/locales/kk_20251019171645.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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": "Алдыңғы" - } -} \ No newline at end of file diff --git a/.history/locales/kk_20251019181611.json b/.history/locales/kk_20251019181611.json deleted file mode 100644 index 493faeb..0000000 --- a/.history/locales/kk_20251019181611.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "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": "Алдыңғы" - } -} \ No newline at end of file diff --git a/.history/locales/kk_20251025214933.json b/.history/locales/kk_20251025214933.json new file mode 100644 index 0000000..0660234 --- /dev/null +++ b/.history/locales/kk_20251025214933.json @@ -0,0 +1,424 @@ +{ + "navigation": { + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "contact": "Байланыс", + "calculator": "Калькулятор", + "admin": "Әкімші" + }, + "hero": { + "title": { + "smart": "Ақылды", + "solutions": "Шешімдер" + }, + "subtitle": "Инновациялық технологиялармен бизнесіңізді дамытыңыз", + "description": "Бизнесіңіздің цифрлық трансформациясы үшін инновациялық веб-дамыту, мобильді қосымшалар, UI/UX дизайн", + "cta": { + "start": "Бастау", + "portfolio": "Портфолионы көру" + } + }, + "services": { + "title": { + "our": "Біздің", + "services": "Қызметтер" + }, + "subtitle": "Идеяларыңызды шындыққа айналдыру үшін кәсіби дамыту қызметтері", + "description": "Озық технологиялар мен шығармашылық идеяларды пайдаланған цифрлық шешімдер", + "view_all": "Барлық қызметтерді көру", + "web": { + "title": "Веб-дамыту", + "description": "Бейімделетін веб-сайттар мен веб-қосымшалар", + "price": "$500-дан бастап" + }, + "mobile": { + "title": "Мобильді қосымшалар", + "description": "iOS және Android үшін жергілікті қосымшалар дамыту", + "price": "$1,000-нан бастап" + }, + "design": { + "title": "UI/UX Дизайн", + "description": "Интерфейс пен пайдаланушы тәжірибесінің дизайны", + "price": "$300-дан бастап" + }, + "marketing": { + "title": "Цифрлық маркетинг", + "description": "SEO, SMM, жарнама басқаруы", + "price": "$200-ден бастап" + }, + "meta": { + "title": "Қызметтер", + "description": "SmartSolTech-тің кәсіби қызметтерін танысыңыз. Веб-дамыту, мобильді қосымшалар, UI/UX дизайн, цифрлық маркетинг және басқа технологиялық шешімдер.", + "keywords": "веб-дамыту, мобильді қосымшалар, UI/UX дизайн, цифрлық маркетинг, технологиялық шешімдер, SmartSolTech" + }, + "hero": { + "title": "Біздің", + "title_highlight": "Қызметтер", + "subtitle": "Инновациялық технологиялармен бизнес өсуін қолдау" + }, + "cards": { + "starting_price": "Бастапқы баға", + "consultation": "кеңес беру", + "contact": "Байланысу", + "calculate_cost": "Құнын есептеу", + "popular": "Танымал", + "coming_soon": "Қызметтер жақында пайда болады", + "coming_soon_desc": "Жақында біз әртүрлі қызметтерді ұсынамыз!" + }, + "process": { + "title": "Жоба іске асыру процесі", + "subtitle": "Біз жобаларды жүйелі және кәсіби тәсілмен жүргіземіз", + "step1": { + "title": "Кеңес беру және жоспарлау", + "description": "Клиенттің талаптарын дәл түсініп, оңтайлы шешімді жоспарлаймыз" + }, + "step2": { + "title": "Дизайн және жобалау", + "description": "Пайдаланушыға бағытталған интуитивті дизайн мен сенімді жүйе архитектурасын жобалаймыз" + }, + "step3": { + "title": "Дамыту және іске асыру", + "description": "Ең жаңа технологиялар мен ең жақсы тәжірибелерді пайдаланып тиімді және кеңейтілетін шешімдер дамытамыз" + }, + "step4": { + "title": "Тестілеу және енгізу", + "description": "Мұқият тестілеу арқылы сапаны қамтамасыз етіп, тұрақты енгізуді жүргіземіз" + } + }, + "why_choose": { + "title": "Неліктен SmartSolTech таңдау керек?", + "modern_tech": { + "title": "Заманауи технологияларды пайдалану", + "description": "Үнемі ең жаңа технологиялық трендтерді қадағалап, дәлелденген технологиялық стекті пайдаланып болашаққа бағытталған шешімдер ұсынамыз." + }, + "expert_team": { + "title": "Сарапшылар командасы", + "description": "Әр саладағы сарапшылардан тұратын команда ынтымақтасып ең жоғары сапалы нәтижелерді қамтамасыз етеді." + }, + "fast_response": { + "title": "Жылдам жауап", + "description": "Жылдам коммуникация және тиімді жоба басқаруы арқылы белгіленген мерзімде жобаларды аяқтаймыз." + }, + "continuous_support": { + "title": "Үздіксіз қолдау", + "description": "Жоба аяқталғаннан кейін де үздіксіз қызмет көрсету және техникалық қолдау арқылы ұзақ мерзімді серіктестікті сақтаймыз." + }, + "quality_guarantee": { + "title": "Сапа кепілдігі", + "subtitle": "Клиенттердің қанағаттануы үшін\nЕң жоғары сапалы қызмет" + } + }, + "cta": { + "title": "Жобаңызды бастауға дайынсыз ба?", + "subtitle": "Тегін кеңес беру арқылы оңтайлы шешімді ұсынамыз", + "free_consultation": "Тегін кеңес беруге өтініш беру", + "calculate_cost": "Құнын есептеу", + "view_portfolio": "Портфолионы көру" + } + }, + "portfolio": { + "title": { + "recent": "Соңғы", + "projects": "Жобалар" + }, + "subtitle": "Сәтті аяқталған жобалармен танысыңыз", + "description": "Клиенттердің табысы үшін орындалған жобаларды көріңіз", + "view_details": "Толық ақпарат", + "view_all": "Барлық портфолионы көру", + "categories": { + "all": "Барлығы", + "web": "Веб-дамыту", + "mobile": "Мобильді қосымшалар", + "uiux": "UI/UX Дизайн" + }, + "project_details": "Жоба егжей-тегжейлері", + "default": { + "ecommerce": "Электрондық сауда", + "title": "Электрондық сауда платформасы", + "description": "Интуитивті интерфейсі бар заманауи интернет-сауда шешімі" + }, + "meta": { + "title": "Портфолио", + "description": "SmartSolTech-тің әртүрлі жобалары мен табыс тарихтарын танысыңыз. Веб-дамыту, мобильді қосымшалар, UI/UX дизайн портфолиосы.", + "keywords": "портфолио, веб-дамыту, мобильді қосымшалар, UI/UX дизайн, жобалар, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Біздің портфолио", + "subtitle": "Инновациялық жобалар мен шығармашылық шешімдерді ашыңыз", + "categories": { + "all": "Барлығы", + "web-development": "Веб-дамыту", + "mobile-app": "Мобильді қосымшалар", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифрлық маркетинг" + }, + "buttons": { + "details": "Толық ақпарат", + "projectDetails": "Жоба егжей-тегжейлері", + "loadMore": "Көбірек жобаларды жүктеу", + "contact": "Жоба тапсырысу", + "calculate": "Құнын есептеу" + }, + "empty": { + "title": "Портфолио әзірше бос", + "subtitle": "Жақында біз керемет жобаларды таныстырамыз!" + }, + "cta": { + "title": "Келесі жобаның жұлдызы болыңыз", + "subtitle": "Бізбен бірге инновациялық цифрлық шешімдер жасаңыз" + }, + "labels": { + "featured": "ҰСЫНЫЛҒАН", + "views": "көрініс", + "likes": "ұнату" + } + }, + "calculator": { + "title": "Жоба құнының калькуляторы", + "subtitle": "Нақты уақытта дәл бағалау алу үшін қажетті қызметтер мен талаптарды таңдаңыз", + "next_step": "Келесі қадам", + "prev_step": "Алдыңғы қадам", + "calculate": "Есептеу", + "reset": "Қайта бастау", + "meta": { + "title": "Жоба құнының калькуляторы", + "description": "Біздің интерактивті калькулятормен веб-дамыту, мобильді қосымша немесе дизайн жобаңыздың құнын есептеңіз" + }, + "cta": { + "title": "Жобаңыздың бағасын тексеріңіз", + "subtitle": "Нақты уақытта құнды есептеу үшін қажетті қызметтер мен талаптарды таңдаңыз", + "button": "Құн калькуляторын пайдалану" + }, + "step1": { + "title": "Қызметтеріңізді таңдаңыз", + "nav_title": "1-қадам: Қызметтер", + "subtitle": "Жобаңыз үшін қажетті қызметтерді таңдаңыз" + }, + "step2": { + "title": "Жоба егжей-тегжейлері", + "nav_title": "2-қадам: Егжей-тегжейлер", + "subtitle": "Жобаңыздың күрделілігі мен мерзімдері туралы айтыңыз" + }, + "complexity": { + "title": "Жоба күрделілігі", + "simple": "Қарапайым", + "simple_desc": "Негізгі функциялар мен стандартты функционал", + "medium": "Орташа", + "medium_desc": "Пайдаланушы функциялары мен интеграциялар", + "complex": "Күрделі", + "complex_desc": "Қосымша функциялар мен күрделі интеграциялар" + }, + "timeline": { + "title": "Жоба мерзімдері", + "standard": "Стандартты (4-8 апта)", + "standard_desc": "Қалыпты дамыту мерзімдері", + "rush": "Шұғыл (2-4 апта)", + "rush_desc": "Қосымша құнмен жылдамдатылған жеткізу", + "extended": "Кеңейтілген (8+ апта)", + "extended_desc": "Құн оңтайландырумен икемді мерзімдер" + }, + "result": { + "title": "Жоба бағасы", + "subtitle": "Сіздің есептелген жоба құны", + "nav_title": "Баға", + "estimated_price": "Есептелген баға", + "price_note": "Бұл шамамен құн. Соңғы баға жобаның күрделілігіне байланысты өзгеруі мүмкін.", + "summary": "Жоба қорытындысы", + "get_quote": "Толық ұсыныс алу", + "contact_note": "Толық кеңес беру және дәл ұсыныс алу үшін бізбен байланысыңыз.", + "recalculate": "Қайта есептеу", + "selected_services": "Таңдалған қызметтер", + "complexity": "Күрделілік", + "timeline": "Мерзімдер" + } + }, + "contact": { + "hero": { + "title": "Бізбен байланысыңыз", + "subtitle": "Біз сіздің идеяларыңызды өмірге келтіруге көмектесуге дайынбыз" + }, + "ready_title": "Жобаңызды бастауға дайынсыз ба?", + "ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.", + "form": { + "title": "Жоба сұранысы", + "name": "Аты", + "email": "Электрондық пошта", + "phone": "Телефон", + "message": "Хабарлама", + "submit": "Сұраныс жіберу", + "success": "Сұраныс сәтті жіберілді", + "error": "Сұранысты жіберу кезінде қате пайда болды", + "service": { + "title": "Қызығушылық қызметі", + "select": "Қызығушылық қызметін таңдаңыз", + "web": "Веб-дамыту", + "mobile": "Мобильді қосымша", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Кеңес беру", + "other": "Басқа" + } + }, + "info": { + "title": "Байланыс ақпараты" + }, + "phone": { + "title": "Телефон арқылы сұраныс", + "number": "+82-2-1234-5678", + "hours": "Дс-Жм 9:00-18:00" + }, + "email": { + "title": "Электрондық пошта арқылы сұраныс", + "address": "info@smartsoltech.co.kr", + "response": "24 сағат ішінде жауап" + }, + "telegram": { + "title": "Telegram", + "subtitle": "Жылдам жауап алу үшін" + }, + "address": { + "title": "Кеңсе мекенжайы", + "line1": "Тегеран-ро көшесі, 123, Каннам-гу", + "line2": "Сеул, Оңтүстік Корея" + }, + "cta": { + "ready": "Дайынсыз ба?", + "start": "Бастау", + "question": "Сұрақтарыңыз бар ма?", + "subtitle": "Біз жобалар бойынша кеңес береміз" + }, + "meta": { + "title": "Байланыс", + "description": "Жоба сұранысы немесе кеңес алу үшін кез келген уақытта бізбен байланысыңыз" + } + }, + "about": { + "hero": { + "title": "SmartSolTech туралы", + "subtitle": "Инновациялар мен технологиялармен болашақты жасаймыз" + }, + "company": { + "title": "Компания туралы ақпарат", + "description1": "SmartSolTech - 2020 жылы құрылған технологиялық компания, веб-дамыту, мобильді қосымшалар дамыту және UI/UX дизайн саласындағы сараптамасымен танылған.", + "description2": "Біз клиенттердің қажеттіліктерін дәл түсінеміз және ең жаңа технологияларды пайдаланып инновациялық шешімдер ұсынамыз." + }, + "stats": { + "projects": "Аяқталған жобалар", + "experience": "Жыл тәжірибе", + "clients": "Қанағаттанған клиенттер" + }, + "mission": { + "title": "Біздің миссия", + "description": "Біздің миссиямыз - технологиялар арқылы клиенттердің бизнес өсуін қолдау және цифрлық инновацияларға көшбасшылық ету." + }, + "values": { + "innovation": { + "title": "Инновациялар", + "description": "Біз үздіксіз зерттеулер мен озық технологияларды енгізу арқылы инновациялық шешімдер ұсынамыз." + }, + "quality": { + "title": "Сапа", + "description": "Біз жоғары сапа стандарттарын ұстанамыз және клиенттер қанағаттанатын жоғары сапалы өнімдер ұсынамыз." + }, + "partnership": { + "title": "Серіктестік", + "description": "Біз клиенттермен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз." + } + }, + "cta": { + "title": "Біз бірге өсеміз", + "subtitle": "Идеяларыңызды шындыққа айналдырыңыз", + "button": "Бізбен байланысу" + }, + "meta": { + "title": "Біз туралы", + "description": "SmartSolTech - инновациялық технологиялармен клиенттердің бизнес өсуін қолдайтын кәсіби дамыту компаниясы" + } + }, + "footer": { + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы", + "company": { + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы" + }, + "links": { + "title": "Жылдам сілтемелер" + }, + "contact": { + "title": "Байланыс", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "Тегеран-ро көшесі, 123, Каннам-гу, Сеул" + }, + "copyright": "© {{year}} SmartSolTech. Барлық құқықтар қорғалған.", + "privacy": "Құпиялылық саясаты", + "terms": "Пайдалану шарттары" + }, + "theme": { + "light": "Ашық тема", + "dark": "Қараңғы тема", + "toggle": "Теманы ауыстыру" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "Жүктелуде...", + "error": "Қате пайда болды", + "success": "Сәтті", + "view_more": "Көбірек көру", + "back": "Артқа", + "next": "Келесі", + "previous": "Алдыңғы", + "view_details": "Толық ақпарат" + }, + "meta": { + "description": "SmartSolTech - Инновациялық веб-дамыту, мобильді қосымшалар дамыту, UI/UX дизайн", + "keywords": "веб-дамыту, мобильді қосымшалар, UI/UX дизайн, Корея", + "title": "SmartSolTech" + }, + "nav": { + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "calculator": "Калькулятор" + }, + "admin": { + "login": "Әкімші панеліне кіру", + "dashboard": "Басқару панелі", + "title": "SmartSolTech Әкімші" + }, + "company": { + "name": "SmartSolTech", + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "Бет табылмады", + "error_occurred": "Қате пайда болды", + "title": "Қате - SmartSolTech", + "default_title": "Қате пайда болды", + "default_message": "Сұранысты өңдеу кезінде мәселе туындады.", + "back_home": "Басты бетке оралу", + "go_back": "Артқа оралу", + "need_help": "Көмек керек пе?", + "help_message": "Егер мәселе қайталанса, кез келген уақытта бізбен байланысыңыз.", + "contact_support": "Қолдау қызметімен байланысу" + }, + "pages": { + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "contact": "Байланыс", + "calculator": "Калькулятор" + } +} \ No newline at end of file diff --git a/.history/locales/kk_20251025215055.json b/.history/locales/kk_20251025215055.json new file mode 100644 index 0000000..0660234 --- /dev/null +++ b/.history/locales/kk_20251025215055.json @@ -0,0 +1,424 @@ +{ + "navigation": { + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "contact": "Байланыс", + "calculator": "Калькулятор", + "admin": "Әкімші" + }, + "hero": { + "title": { + "smart": "Ақылды", + "solutions": "Шешімдер" + }, + "subtitle": "Инновациялық технологиялармен бизнесіңізді дамытыңыз", + "description": "Бизнесіңіздің цифрлық трансформациясы үшін инновациялық веб-дамыту, мобильді қосымшалар, UI/UX дизайн", + "cta": { + "start": "Бастау", + "portfolio": "Портфолионы көру" + } + }, + "services": { + "title": { + "our": "Біздің", + "services": "Қызметтер" + }, + "subtitle": "Идеяларыңызды шындыққа айналдыру үшін кәсіби дамыту қызметтері", + "description": "Озық технологиялар мен шығармашылық идеяларды пайдаланған цифрлық шешімдер", + "view_all": "Барлық қызметтерді көру", + "web": { + "title": "Веб-дамыту", + "description": "Бейімделетін веб-сайттар мен веб-қосымшалар", + "price": "$500-дан бастап" + }, + "mobile": { + "title": "Мобильді қосымшалар", + "description": "iOS және Android үшін жергілікті қосымшалар дамыту", + "price": "$1,000-нан бастап" + }, + "design": { + "title": "UI/UX Дизайн", + "description": "Интерфейс пен пайдаланушы тәжірибесінің дизайны", + "price": "$300-дан бастап" + }, + "marketing": { + "title": "Цифрлық маркетинг", + "description": "SEO, SMM, жарнама басқаруы", + "price": "$200-ден бастап" + }, + "meta": { + "title": "Қызметтер", + "description": "SmartSolTech-тің кәсіби қызметтерін танысыңыз. Веб-дамыту, мобильді қосымшалар, UI/UX дизайн, цифрлық маркетинг және басқа технологиялық шешімдер.", + "keywords": "веб-дамыту, мобильді қосымшалар, UI/UX дизайн, цифрлық маркетинг, технологиялық шешімдер, SmartSolTech" + }, + "hero": { + "title": "Біздің", + "title_highlight": "Қызметтер", + "subtitle": "Инновациялық технологиялармен бизнес өсуін қолдау" + }, + "cards": { + "starting_price": "Бастапқы баға", + "consultation": "кеңес беру", + "contact": "Байланысу", + "calculate_cost": "Құнын есептеу", + "popular": "Танымал", + "coming_soon": "Қызметтер жақында пайда болады", + "coming_soon_desc": "Жақында біз әртүрлі қызметтерді ұсынамыз!" + }, + "process": { + "title": "Жоба іске асыру процесі", + "subtitle": "Біз жобаларды жүйелі және кәсіби тәсілмен жүргіземіз", + "step1": { + "title": "Кеңес беру және жоспарлау", + "description": "Клиенттің талаптарын дәл түсініп, оңтайлы шешімді жоспарлаймыз" + }, + "step2": { + "title": "Дизайн және жобалау", + "description": "Пайдаланушыға бағытталған интуитивті дизайн мен сенімді жүйе архитектурасын жобалаймыз" + }, + "step3": { + "title": "Дамыту және іске асыру", + "description": "Ең жаңа технологиялар мен ең жақсы тәжірибелерді пайдаланып тиімді және кеңейтілетін шешімдер дамытамыз" + }, + "step4": { + "title": "Тестілеу және енгізу", + "description": "Мұқият тестілеу арқылы сапаны қамтамасыз етіп, тұрақты енгізуді жүргіземіз" + } + }, + "why_choose": { + "title": "Неліктен SmartSolTech таңдау керек?", + "modern_tech": { + "title": "Заманауи технологияларды пайдалану", + "description": "Үнемі ең жаңа технологиялық трендтерді қадағалап, дәлелденген технологиялық стекті пайдаланып болашаққа бағытталған шешімдер ұсынамыз." + }, + "expert_team": { + "title": "Сарапшылар командасы", + "description": "Әр саладағы сарапшылардан тұратын команда ынтымақтасып ең жоғары сапалы нәтижелерді қамтамасыз етеді." + }, + "fast_response": { + "title": "Жылдам жауап", + "description": "Жылдам коммуникация және тиімді жоба басқаруы арқылы белгіленген мерзімде жобаларды аяқтаймыз." + }, + "continuous_support": { + "title": "Үздіксіз қолдау", + "description": "Жоба аяқталғаннан кейін де үздіксіз қызмет көрсету және техникалық қолдау арқылы ұзақ мерзімді серіктестікті сақтаймыз." + }, + "quality_guarantee": { + "title": "Сапа кепілдігі", + "subtitle": "Клиенттердің қанағаттануы үшін\nЕң жоғары сапалы қызмет" + } + }, + "cta": { + "title": "Жобаңызды бастауға дайынсыз ба?", + "subtitle": "Тегін кеңес беру арқылы оңтайлы шешімді ұсынамыз", + "free_consultation": "Тегін кеңес беруге өтініш беру", + "calculate_cost": "Құнын есептеу", + "view_portfolio": "Портфолионы көру" + } + }, + "portfolio": { + "title": { + "recent": "Соңғы", + "projects": "Жобалар" + }, + "subtitle": "Сәтті аяқталған жобалармен танысыңыз", + "description": "Клиенттердің табысы үшін орындалған жобаларды көріңіз", + "view_details": "Толық ақпарат", + "view_all": "Барлық портфолионы көру", + "categories": { + "all": "Барлығы", + "web": "Веб-дамыту", + "mobile": "Мобильді қосымшалар", + "uiux": "UI/UX Дизайн" + }, + "project_details": "Жоба егжей-тегжейлері", + "default": { + "ecommerce": "Электрондық сауда", + "title": "Электрондық сауда платформасы", + "description": "Интуитивті интерфейсі бар заманауи интернет-сауда шешімі" + }, + "meta": { + "title": "Портфолио", + "description": "SmartSolTech-тің әртүрлі жобалары мен табыс тарихтарын танысыңыз. Веб-дамыту, мобильді қосымшалар, UI/UX дизайн портфолиосы.", + "keywords": "портфолио, веб-дамыту, мобильді қосымшалар, UI/UX дизайн, жобалар, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Біздің портфолио", + "subtitle": "Инновациялық жобалар мен шығармашылық шешімдерді ашыңыз", + "categories": { + "all": "Барлығы", + "web-development": "Веб-дамыту", + "mobile-app": "Мобильді қосымшалар", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифрлық маркетинг" + }, + "buttons": { + "details": "Толық ақпарат", + "projectDetails": "Жоба егжей-тегжейлері", + "loadMore": "Көбірек жобаларды жүктеу", + "contact": "Жоба тапсырысу", + "calculate": "Құнын есептеу" + }, + "empty": { + "title": "Портфолио әзірше бос", + "subtitle": "Жақында біз керемет жобаларды таныстырамыз!" + }, + "cta": { + "title": "Келесі жобаның жұлдызы болыңыз", + "subtitle": "Бізбен бірге инновациялық цифрлық шешімдер жасаңыз" + }, + "labels": { + "featured": "ҰСЫНЫЛҒАН", + "views": "көрініс", + "likes": "ұнату" + } + }, + "calculator": { + "title": "Жоба құнының калькуляторы", + "subtitle": "Нақты уақытта дәл бағалау алу үшін қажетті қызметтер мен талаптарды таңдаңыз", + "next_step": "Келесі қадам", + "prev_step": "Алдыңғы қадам", + "calculate": "Есептеу", + "reset": "Қайта бастау", + "meta": { + "title": "Жоба құнының калькуляторы", + "description": "Біздің интерактивті калькулятормен веб-дамыту, мобильді қосымша немесе дизайн жобаңыздың құнын есептеңіз" + }, + "cta": { + "title": "Жобаңыздың бағасын тексеріңіз", + "subtitle": "Нақты уақытта құнды есептеу үшін қажетті қызметтер мен талаптарды таңдаңыз", + "button": "Құн калькуляторын пайдалану" + }, + "step1": { + "title": "Қызметтеріңізді таңдаңыз", + "nav_title": "1-қадам: Қызметтер", + "subtitle": "Жобаңыз үшін қажетті қызметтерді таңдаңыз" + }, + "step2": { + "title": "Жоба егжей-тегжейлері", + "nav_title": "2-қадам: Егжей-тегжейлер", + "subtitle": "Жобаңыздың күрделілігі мен мерзімдері туралы айтыңыз" + }, + "complexity": { + "title": "Жоба күрделілігі", + "simple": "Қарапайым", + "simple_desc": "Негізгі функциялар мен стандартты функционал", + "medium": "Орташа", + "medium_desc": "Пайдаланушы функциялары мен интеграциялар", + "complex": "Күрделі", + "complex_desc": "Қосымша функциялар мен күрделі интеграциялар" + }, + "timeline": { + "title": "Жоба мерзімдері", + "standard": "Стандартты (4-8 апта)", + "standard_desc": "Қалыпты дамыту мерзімдері", + "rush": "Шұғыл (2-4 апта)", + "rush_desc": "Қосымша құнмен жылдамдатылған жеткізу", + "extended": "Кеңейтілген (8+ апта)", + "extended_desc": "Құн оңтайландырумен икемді мерзімдер" + }, + "result": { + "title": "Жоба бағасы", + "subtitle": "Сіздің есептелген жоба құны", + "nav_title": "Баға", + "estimated_price": "Есептелген баға", + "price_note": "Бұл шамамен құн. Соңғы баға жобаның күрделілігіне байланысты өзгеруі мүмкін.", + "summary": "Жоба қорытындысы", + "get_quote": "Толық ұсыныс алу", + "contact_note": "Толық кеңес беру және дәл ұсыныс алу үшін бізбен байланысыңыз.", + "recalculate": "Қайта есептеу", + "selected_services": "Таңдалған қызметтер", + "complexity": "Күрделілік", + "timeline": "Мерзімдер" + } + }, + "contact": { + "hero": { + "title": "Бізбен байланысыңыз", + "subtitle": "Біз сіздің идеяларыңызды өмірге келтіруге көмектесуге дайынбыз" + }, + "ready_title": "Жобаңызды бастауға дайынсыз ба?", + "ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.", + "form": { + "title": "Жоба сұранысы", + "name": "Аты", + "email": "Электрондық пошта", + "phone": "Телефон", + "message": "Хабарлама", + "submit": "Сұраныс жіберу", + "success": "Сұраныс сәтті жіберілді", + "error": "Сұранысты жіберу кезінде қате пайда болды", + "service": { + "title": "Қызығушылық қызметі", + "select": "Қызығушылық қызметін таңдаңыз", + "web": "Веб-дамыту", + "mobile": "Мобильді қосымша", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Кеңес беру", + "other": "Басқа" + } + }, + "info": { + "title": "Байланыс ақпараты" + }, + "phone": { + "title": "Телефон арқылы сұраныс", + "number": "+82-2-1234-5678", + "hours": "Дс-Жм 9:00-18:00" + }, + "email": { + "title": "Электрондық пошта арқылы сұраныс", + "address": "info@smartsoltech.co.kr", + "response": "24 сағат ішінде жауап" + }, + "telegram": { + "title": "Telegram", + "subtitle": "Жылдам жауап алу үшін" + }, + "address": { + "title": "Кеңсе мекенжайы", + "line1": "Тегеран-ро көшесі, 123, Каннам-гу", + "line2": "Сеул, Оңтүстік Корея" + }, + "cta": { + "ready": "Дайынсыз ба?", + "start": "Бастау", + "question": "Сұрақтарыңыз бар ма?", + "subtitle": "Біз жобалар бойынша кеңес береміз" + }, + "meta": { + "title": "Байланыс", + "description": "Жоба сұранысы немесе кеңес алу үшін кез келген уақытта бізбен байланысыңыз" + } + }, + "about": { + "hero": { + "title": "SmartSolTech туралы", + "subtitle": "Инновациялар мен технологиялармен болашақты жасаймыз" + }, + "company": { + "title": "Компания туралы ақпарат", + "description1": "SmartSolTech - 2020 жылы құрылған технологиялық компания, веб-дамыту, мобильді қосымшалар дамыту және UI/UX дизайн саласындағы сараптамасымен танылған.", + "description2": "Біз клиенттердің қажеттіліктерін дәл түсінеміз және ең жаңа технологияларды пайдаланып инновациялық шешімдер ұсынамыз." + }, + "stats": { + "projects": "Аяқталған жобалар", + "experience": "Жыл тәжірибе", + "clients": "Қанағаттанған клиенттер" + }, + "mission": { + "title": "Біздің миссия", + "description": "Біздің миссиямыз - технологиялар арқылы клиенттердің бизнес өсуін қолдау және цифрлық инновацияларға көшбасшылық ету." + }, + "values": { + "innovation": { + "title": "Инновациялар", + "description": "Біз үздіксіз зерттеулер мен озық технологияларды енгізу арқылы инновациялық шешімдер ұсынамыз." + }, + "quality": { + "title": "Сапа", + "description": "Біз жоғары сапа стандарттарын ұстанамыз және клиенттер қанағаттанатын жоғары сапалы өнімдер ұсынамыз." + }, + "partnership": { + "title": "Серіктестік", + "description": "Біз клиенттермен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз." + } + }, + "cta": { + "title": "Біз бірге өсеміз", + "subtitle": "Идеяларыңызды шындыққа айналдырыңыз", + "button": "Бізбен байланысу" + }, + "meta": { + "title": "Біз туралы", + "description": "SmartSolTech - инновациялық технологиялармен клиенттердің бизнес өсуін қолдайтын кәсіби дамыту компаниясы" + } + }, + "footer": { + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы", + "company": { + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы" + }, + "links": { + "title": "Жылдам сілтемелер" + }, + "contact": { + "title": "Байланыс", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "Тегеран-ро көшесі, 123, Каннам-гу, Сеул" + }, + "copyright": "© {{year}} SmartSolTech. Барлық құқықтар қорғалған.", + "privacy": "Құпиялылық саясаты", + "terms": "Пайдалану шарттары" + }, + "theme": { + "light": "Ашық тема", + "dark": "Қараңғы тема", + "toggle": "Теманы ауыстыру" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "Жүктелуде...", + "error": "Қате пайда болды", + "success": "Сәтті", + "view_more": "Көбірек көру", + "back": "Артқа", + "next": "Келесі", + "previous": "Алдыңғы", + "view_details": "Толық ақпарат" + }, + "meta": { + "description": "SmartSolTech - Инновациялық веб-дамыту, мобильді қосымшалар дамыту, UI/UX дизайн", + "keywords": "веб-дамыту, мобильді қосымшалар, UI/UX дизайн, Корея", + "title": "SmartSolTech" + }, + "nav": { + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "calculator": "Калькулятор" + }, + "admin": { + "login": "Әкімші панеліне кіру", + "dashboard": "Басқару панелі", + "title": "SmartSolTech Әкімші" + }, + "company": { + "name": "SmartSolTech", + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "Бет табылмады", + "error_occurred": "Қате пайда болды", + "title": "Қате - SmartSolTech", + "default_title": "Қате пайда болды", + "default_message": "Сұранысты өңдеу кезінде мәселе туындады.", + "back_home": "Басты бетке оралу", + "go_back": "Артқа оралу", + "need_help": "Көмек керек пе?", + "help_message": "Егер мәселе қайталанса, кез келген уақытта бізбен байланысыңыз.", + "contact_support": "Қолдау қызметімен байланысу" + }, + "pages": { + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "contact": "Байланыс", + "calculator": "Калькулятор" + } +} \ No newline at end of file diff --git a/.history/locales/ko_20251019171450.json b/.history/locales/ko_20251019171450.json deleted file mode 100644 index 60583db..0000000 --- a/.history/locales/ko_20251019171450.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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": "이전" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019171645.json b/.history/locales/ko_20251019171645.json deleted file mode 100644 index 60583db..0000000 --- a/.history/locales/ko_20251019171645.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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": "이전" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019181428.json b/.history/locales/ko_20251019181428.json deleted file mode 100644 index 15e58cf..0000000 --- a/.history/locales/ko_20251019181428.json +++ /dev/null @@ -1,227 +0,0 @@ -{ - "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": "이전" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019181450.json b/.history/locales/ko_20251019181450.json deleted file mode 100644 index 9e5e2a9..0000000 --- a/.history/locales/ko_20251019181450.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "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": "이전" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019181629.json b/.history/locales/ko_20251019181629.json deleted file mode 100644 index 9e5e2a9..0000000 --- a/.history/locales/ko_20251019181629.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "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": "이전" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019182536.json b/.history/locales/ko_20251019182536.json deleted file mode 100644 index 2d12a27..0000000 --- a/.history/locales/ko_20251019182536.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019182628.json b/.history/locales/ko_20251019182628.json deleted file mode 100644 index 2d12a27..0000000 --- a/.history/locales/ko_20251019182628.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019203946.json b/.history/locales/ko_20251019203946.json deleted file mode 100644 index dd925e3..0000000 --- a/.history/locales/ko_20251019203946.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기", - "contact": "문의하기" - }, - "language": { - "ko": "한국어", - "korean": "한국어", - "english": "영어", - "russian": "러시아어", - "kazakh": "카자흐어" - }, - "theme": { - "toggle": "테마 전환" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "view_all": "모든 서비스 보기" - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_all": "모든 프로젝트 보기", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - } - }, - "common": { - "view_details": "자세히 보기" - }, - "about": { - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요" - } - }, - "contact": { - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "message": "메시지", - "submit": "문의 보내기", - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - } - }, - "calculator": { - "meta": { - "title": "견적 계산기", - "description": "프로젝트 예상 비용을 간편하게 계산해보세요" - }, - "title": "견적 계산기", - "subtitle": "프로젝트 예상 비용을 확인해보세요", - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기", - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간" - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "social": { - "follow": "팔로우하기" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251019204000.json b/.history/locales/ko_20251019204000.json deleted file mode 100644 index dd925e3..0000000 --- a/.history/locales/ko_20251019204000.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기", - "contact": "문의하기" - }, - "language": { - "ko": "한국어", - "korean": "한국어", - "english": "영어", - "russian": "러시아어", - "kazakh": "카자흐어" - }, - "theme": { - "toggle": "테마 전환" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "view_all": "모든 서비스 보기" - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_all": "모든 프로젝트 보기", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - } - }, - "common": { - "view_details": "자세히 보기" - }, - "about": { - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요" - } - }, - "contact": { - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "message": "메시지", - "submit": "문의 보내기", - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - } - }, - "calculator": { - "meta": { - "title": "견적 계산기", - "description": "프로젝트 예상 비용을 간편하게 계산해보세요" - }, - "title": "견적 계산기", - "subtitle": "프로젝트 예상 비용을 확인해보세요", - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기", - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간" - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "social": { - "follow": "팔로우하기" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021185144.json b/.history/locales/ko_20251021185144.json deleted file mode 100644 index 73fd3b7..0000000 --- a/.history/locales/ko_20251021185144.json +++ /dev/null @@ -1,429 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", - "cta_primary": "Start Project", - "cta_secondary": "View Portfolio", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "title_highlight": "서비스", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - }, - "view_all": "모든 서비스 보기", - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - } - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "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 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": "이름", - "email": "이메일", - "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": "메시지", - "submit": "문의 보내기", - "title": "프로젝트 문의", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - } - }, - "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": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "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": "함께 성장하겠습니다", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "당신의 아이디어를 현실로 만들어보세요" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021185152.json b/.history/locales/ko_20251021185152.json deleted file mode 100644 index 3878de2..0000000 --- a/.history/locales/ko_20251021185152.json +++ /dev/null @@ -1,429 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "title_highlight": "서비스", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - }, - "view_all": "모든 서비스 보기", - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - } - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "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 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": "이름", - "email": "이메일", - "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": "메시지", - "submit": "문의 보내기", - "title": "프로젝트 문의", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - } - }, - "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": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "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": "함께 성장하겠습니다", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "당신의 아이디어를 현실로 만들어보세요" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021190951.json b/.history/locales/ko_20251021190951.json deleted file mode 100644 index 3878de2..0000000 --- a/.history/locales/ko_20251021190951.json +++ /dev/null @@ -1,429 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "title_highlight": "서비스", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - }, - "view_all": "모든 서비스 보기", - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - } - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "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 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": "이름", - "email": "이메일", - "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": "메시지", - "submit": "문의 보내기", - "title": "프로젝트 문의", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - } - }, - "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": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "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": "함께 성장하겠습니다", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "당신의 아이디어를 현실로 만들어보세요" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021210103.json b/.history/locales/ko_20251021210103.json deleted file mode 100644 index a7414ef..0000000 --- a/.history/locales/ko_20251021210103.json +++ /dev/null @@ -1,481 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "title_highlight": "서비스", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - }, - "view_all": "모든 서비스 보기", - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application", - "form": { - "name": "이름", - "email": "이메일", - "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": "메시지", - "submit": "문의 보내기", - "title": "프로젝트 문의", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - } - }, - "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": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "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": "함께 성장하겠습니다", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021210120.json b/.history/locales/ko_20251021210120.json deleted file mode 100644 index a97fe4b..0000000 --- a/.history/locales/ko_20251021210120.json +++ /dev/null @@ -1,482 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "title_highlight": "서비스", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - }, - "view_all": "모든 서비스 보기", - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application", - "form": { - "name": "이름", - "email": "이메일", - "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": "메시지", - "submit": "문의 보내기", - "title": "프로젝트 문의", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - } - }, - "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": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "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": "함께 성장하겠습니다", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021210347.json b/.history/locales/ko_20251021210347.json deleted file mode 100644 index a97fe4b..0000000 --- a/.history/locales/ko_20251021210347.json +++ /dev/null @@ -1,482 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - } - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "title_highlight": "서비스", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - }, - "view_all": "모든 서비스 보기", - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "title_highlight": "Projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "view_project": "프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - } - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application", - "form": { - "name": "이름", - "email": "이메일", - "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": "메시지", - "submit": "문의 보내기", - "title": "프로젝트 문의", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - } - }, - "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": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - } - }, - "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": "함께 성장하겠습니다", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - } - }, - "footer": { - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "description": "Digital solution specialist leading innovation", - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard": "Dashboard", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "title": "SmartSolTech Admin", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "description": "Digital solution specialist leading innovation", - "tagline": "Future begins here", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "contact_us": "Contact us", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ko_20251021211024.json b/.history/locales/ko_20251021211024.json deleted file mode 100644 index 7aa2067..0000000 --- a/.history/locales/ko_20251021211024.json +++ /dev/null @@ -1,500 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - }, - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기" - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "view_all": "모든 서비스 보기", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - }, - "title_highlight": "서비스", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - }, - "title_highlight": "Projects", - "view_project": "프로젝트 보기" - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "message": "메시지", - "submit": "문의 보내기", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "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" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "form": { - "title": "프로젝트 문의", - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다", - "service": { - "title": "관심 서비스" - } - }, - "info": { - "title": "연락처 정보" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678", - "hours": "월-금 9:00-18:00" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr", - "response": "24시간 내 응답" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "address": { - "title": "사무실 주소", - "line1": "테헤란로 123, 강남구", - "line2": "서울, 대한민국" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application" - }, - "about": { - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - }, - "title": "Core", - "title_highlight": "Values", - "description": "Core values pursued by SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "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" - }, - "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" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "tagline": "Future begins here", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ko_20251021211047.json b/.history/locales/ko_20251021211047.json deleted file mode 100644 index 81687c8..0000000 --- a/.history/locales/ko_20251021211047.json +++ /dev/null @@ -1,492 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - }, - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기" - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "view_all": "모든 서비스 보기", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - }, - "title_highlight": "서비스", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - }, - "title_highlight": "Projects", - "view_project": "프로젝트 보기" - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "message": "메시지", - "submit": "문의 보내기", - "service": { - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - }, - "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" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "info": { - "title": "연락처 정보" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678", - "hours": "월-금 9:00-18:00" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr", - "response": "24시간 내 응답" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "address": { - "title": "사무실 주소", - "line1": "테헤란로 123, 강남구", - "line2": "서울, 대한민국" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application" - }, - "about": { - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - }, - "title": "Core", - "title_highlight": "Values", - "description": "Core values pursued by SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "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" - }, - "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" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "tagline": "Future begins here", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ko_20251021211057.json b/.history/locales/ko_20251021211057.json deleted file mode 100644 index e4a476d..0000000 --- a/.history/locales/ko_20251021211057.json +++ /dev/null @@ -1,496 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - }, - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기" - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "view_all": "모든 서비스 보기", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - }, - "title_highlight": "서비스", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - }, - "title_highlight": "Projects", - "view_project": "프로젝트 보기" - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "message": "메시지", - "submit": "문의 보내기", - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다", - "service": { - "title": "관심 서비스", - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - } - }, - "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" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "info": { - "title": "연락처 정보" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678", - "hours": "월-금 9:00-18:00" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr", - "response": "24시간 내 응답" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "address": { - "title": "사무실 주소", - "line1": "테헤란로 123, 강남구", - "line2": "서울, 대한민국" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application" - }, - "about": { - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - }, - "title": "Core", - "title_highlight": "Values", - "description": "Core values pursued by SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "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" - }, - "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" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "tagline": "Future begins here", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ko_20251021211426.json b/.history/locales/ko_20251021211426.json deleted file mode 100644 index 50922bc..0000000 --- a/.history/locales/ko_20251021211426.json +++ /dev/null @@ -1,528 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - }, - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기" - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "view_all": "모든 서비스 보기", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - }, - "title_highlight": "서비스", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - }, - "title_highlight": "Projects", - "view_project": "프로젝트 보기" - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "message": "메시지", - "submit": "문의 보내기", - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다", - "service": { - "title": "관심 서비스", - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - } - }, - "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" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "info": { - "title": "연락처 정보" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678", - "hours": "월-금 9:00-18:00" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr", - "response": "24시간 내 응답" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "address": { - "title": "사무실 주소", - "line1": "테헤란로 123, 강남구", - "line2": "서울, 대한민국" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application" - }, - "about": { - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - }, - "title": "Core", - "title_highlight": "Values", - "description": "Core values pursued by SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "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" - }, - "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" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "tagline": "Future begins here", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "portfolio_page": { - "title": "우리의 포트폴리오", - "subtitle": "혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요", - "categories": { - "all": "전체", - "web-development": "웹 개발", - "mobile-app": "모바일 앱", - "ui-ux-design": "UI/UX 디자인", - "branding": "브랜딩", - "marketing": "디지털 마케팅" - }, - "buttons": { - "details": "자세히 보기", - "projectDetails": "프로젝트 상세보기", - "loadMore": "더 많은 프로젝트 보기", - "contact": "프로젝트 문의하기", - "calculate": "비용 계산하기" - }, - "empty": { - "title": "아직 포트폴리오가 없습니다", - "subtitle": "곧 멋진 프로젝트들을 공개할 예정입니다!" - }, - "cta": { - "title": "다음 프로젝트의 주인공이 되어보세요", - "subtitle": "우리와 함께 혁신적인 디지털 솔루션을 만들어보세요" - }, - "labels": { - "featured": "FEATURED", - "views": "조회수", - "likes": "좋아요" - } - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ko_20251021211513.json b/.history/locales/ko_20251021211513.json deleted file mode 100644 index 50922bc..0000000 --- a/.history/locales/ko_20251021211513.json +++ /dev/null @@ -1,528 +0,0 @@ -{ - "navigation": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "contact": "문의하기", - "calculator": "견적 계산기", - "admin": "Admin", - "home - SmartSolTech": "navigation.home - SmartSolTech" - }, - "hero": { - "title": { - "smart": "스마트", - "solutions": "솔루션" - }, - "subtitle": "혁신적인 기술로 당신의 비즈니스를 성장시키세요", - "description": "혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비�니스 디지털 변혁을 이끕니다", - "cta": { - "start": "시작하기", - "portfolio": "포트폴리오 보기" - }, - "cta_primary": "프로젝트 시작", - "cta_secondary": "포트폴리오 보기" - }, - "services": { - "title": { - "our": "우리의", - "services": "서비스" - }, - "subtitle": "전문적인 개발 서비스로 당신의 아이디어를 현실로 만들어드립니다", - "description": "최첨단 기술과 창의적 아이디어로 완성된 디지털 솔루션", - "view_all": "모든 서비스 보기", - "web": { - "title": "웹 개발", - "description": "반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500,000부터" - }, - "mobile": { - "title": "모바일 앱", - "description": "iOS와 Android 네이티브 앱 개발", - "price": "₩1,000,000부터" - }, - "design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 인터페이스 및 경험 디자인", - "price": "₩300,000부터" - }, - "marketing": { - "title": "디지털 마케팅", - "description": "SEO, SNS 마케팅, 광고 운영", - "price": "₩200,000부터" - }, - "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" - }, - "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" - }, - "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" - }, - "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" - } - }, - "title_highlight": "서비스", - "web_development": { - "title": "웹 개발", - "description": "현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발", - "price": "₩500만원~" - }, - "mobile_app": { - "title": "모바일 앱", - "description": "iOS와 Android용 네이티브 및 크로스 플랫폼 앱", - "price": "₩800만원~" - }, - "ui_ux_design": { - "title": "UI/UX 디자인", - "description": "사용자 중심의 직관적이고 아름다운 인터페이스 디자인", - "price": "₩300만원~" - }, - "digital_marketing": { - "title": "디지털 마케팅", - "description": "SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅", - "price": "₩200만원~" - } - }, - "portfolio": { - "title": { - "recent": "최근", - "projects": "프로젝트", - "our": "우리의", - "portfolio": "포트폴리오" - }, - "subtitle": "성공적으로 완료한 프로젝트들을 확인해보세요", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "모든 프로젝트 보기", - "categories": { - "all": "전체", - "web": "웹 개발", - "mobile": "모바일 앱", - "uiux": "UI/UX 디자인" - }, - "project_details": "프로젝트 상세보기", - "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" - }, - "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - }, - "title_highlight": "Projects", - "view_project": "프로젝트 보기" - }, - "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": "빠른 개발 (추가 비용)", - "extended": "여유", - "extended_desc": "충분한 개발 기간 (할인)" - }, - "result": { - "title": "견적 결과", - "subtitle": "프로젝트 예상 비용입니다", - "estimated_price": "예상 비용", - "price_note": "* 정확한 견적은 상담 후 확정됩니다", - "summary": "견적 요약", - "selected_services": "선택된 서비스", - "complexity": "복잡도", - "timeline": "개발 기간", - "get_quote": "정확한 견적 요청", - "recalculate": "다시 계산하기", - "contact_note": "더 정확한 견적을 위해 상담을 받아보세요" - }, - "next_step": "다음 단계", - "prev_step": "이전 단계", - "calculate": "견적 계산하기" - }, - "contact": { - "hero": { - "title": "문의하기", - "subtitle": "여러분의 아이디어를 현실로 만들어드립니다" - }, - "ready_title": "프로젝트를 시작할 준비가 되셨나요?", - "ready_description": "아이디어를 현실로 바꿔보세요. 전문가가 최고의 솔루션을 제공합니다.", - "form": { - "title": "프로젝트 문의", - "name": "이름", - "email": "이메일", - "phone": "전화번호", - "message": "메시지", - "submit": "문의 보내기", - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다", - "service": { - "title": "관심 서비스", - "select": "관심 서비스를 선택하세요", - "web": "웹 개발", - "mobile": "모바일 앱", - "design": "UI/UX 디자인", - "branding": "브랜딩", - "consulting": "컨설팅", - "other": "기타" - } - }, - "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" - }, - "success": "문의가 성공적으로 전송되었습니다", - "error": "문의 전송 중 오류가 발생했습니다" - }, - "info": { - "title": "연락처 정보" - }, - "phone": { - "title": "전화 문의", - "number": "+82-2-1234-5678", - "hours": "월-금 9:00-18:00" - }, - "email": { - "title": "이메일 문의", - "address": "info@smartsoltech.co.kr", - "response": "24시간 내 응답" - }, - "telegram": { - "title": "텔레그램", - "subtitle": "빠른 응답을 원하신다면" - }, - "address": { - "title": "사무실 주소", - "line1": "테헤란로 123, 강남구", - "line2": "서울, 대한민국" - }, - "cta": { - "ready": "준비되셨나요?", - "start": "시작해보세요", - "question": "질문이 있으신가요?", - "subtitle": "프로젝트에 대해 상담해드립니다" - }, - "meta": { - "title": "문의하기", - "description": "프로젝트 문의나 상담이 필요하시면 언제든 연락주세요" - }, - "phone_consultation": "Phone Consultation", - "email_inquiry": "Email Inquiry", - "telegram_chat": "Telegram Chat", - "instant_response": "Instant response available", - "free_consultation": "Free Consultation Application" - }, - "about": { - "hero": { - "title": "스마트솔테크 소개", - "subtitle": "혁신과 기술로 미래를 만들어갑니다" - }, - "company": { - "title": "회사 정보", - "description1": "스마트솔테크는 2020년 설립된 기술 전문 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야에서 전문성을 인정받고 있습니다.", - "description2": "우리는 고객의 니즈를 정확히 파악하고, 최신 기술을 활용하여 혁신적인 솔루션을 제공합니다." - }, - "stats": { - "projects": "완료된 프로젝트", - "experience": "년간 경험", - "clients": "만족한 고객" - }, - "mission": { - "title": "우리의 미션", - "description": "기술을 통해 고객의 비즈니스 성장을 지원하고, 디지털 혁신을 이끌어나가는 것이 우리의 사명입니다." - }, - "values": { - "innovation": { - "title": "혁신", - "description": "최신 기술과 트렌드를 빠르게 적용하여 혁신적인 솔루션을 제공합니다." - }, - "quality": { - "title": "품질", - "description": "높은 품질의 코드와 디자인으로 안정적이고 우수한 제품을 개발합니다." - }, - "partnership": { - "title": "파트너십", - "description": "고객과의 긴밀한 협력을 통해 최상의 결과를 만들어냅니다." - }, - "title": "Core", - "title_highlight": "Values", - "description": "Core values pursued by SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Growth", - "description": "We grow together with customers and pursue continuous learning and development." - } - }, - "cta": { - "title": "함께 성장하겠습니다", - "subtitle": "당신의 아이디어를 현실로 만들어보세요", - "button": "Contact Us", - "description": "Take your business to the next level with SmartSolTech", - "partnership": "Partnership Inquiry", - "portfolio": "View Portfolio" - }, - "meta": { - "title": "회사소개", - "description": "스마트솔테크는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" - }, - "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" - }, - "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" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "빠른 링크", - "privacy": "개인정보처리방침", - "terms": "이용약관", - "sitemap": "사이트맵" - }, - "contact": { - "title": "연락처", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "서울특별시 강남구 테헤란로 123" - }, - "copyright": "© 2024 스마트솔테크. 모든 권리 보유.", - "company": { - "description": "혁신적인 기술로 당신의 비즈니스를 성장시키세요" - }, - "quick_links": "Quick Links", - "services": "Services", - "contact_info": "Contact Information", - "follow_us": "Follow Us", - "rights": "All rights reserved.", - "privacy": "개인정보보호", - "terms": "이용약관", - "social": { - "follow": "팔로우하기" - } - }, - "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "테마 전환" - }, - "language": { - "english": "영어", - "korean": "한국어", - "russian": "러시아어", - "kazakh": "카자흐어", - "ko": "한국어" - }, - "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "자세히 보기" - }, - "meta": { - "description": "스마트솔테크 - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", - "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", - "title": "스마트솔테크" - }, - "nav": { - "home": "홈", - "about": "회사소개", - "services": "서비스", - "portfolio": "포트폴리오", - "calculator": "견적 계산기" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "tagline": "Future begins here", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support", - "contact_us": "Contact us" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "portfolio_page": { - "title": "우리의 포트폴리오", - "subtitle": "혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요", - "categories": { - "all": "전체", - "web-development": "웹 개발", - "mobile-app": "모바일 앱", - "ui-ux-design": "UI/UX 디자인", - "branding": "브랜딩", - "marketing": "디지털 마케팅" - }, - "buttons": { - "details": "자세히 보기", - "projectDetails": "프로젝트 상세보기", - "loadMore": "더 많은 프로젝트 보기", - "contact": "프로젝트 문의하기", - "calculate": "비용 계산하기" - }, - "empty": { - "title": "아직 포트폴리오가 없습니다", - "subtitle": "곧 멋진 프로젝트들을 공개할 예정입니다!" - }, - "cta": { - "title": "다음 프로젝트의 주인공이 되어보세요", - "subtitle": "우리와 함께 혁신적인 디지털 솔루션을 만들어보세요" - }, - "labels": { - "featured": "FEATURED", - "views": "조회수", - "likes": "좋아요" - } - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ko_20251025214817.json b/.history/locales/ko_20251025214817.json new file mode 100644 index 0000000..d408b75 --- /dev/null +++ b/.history/locales/ko_20251025214817.json @@ -0,0 +1,424 @@ +{ + "navigation": { + "home": "홈", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "contact": "연락처", + "calculator": "비용계산기", + "admin": "관리자" + }, + "hero": { + "title": { + "smart": "스마트", + "solutions": "솔루션" + }, + "subtitle": "혁신적인 기술로 비즈니스를 성장시키세요", + "description": "비즈니스의 디지털 전환을 이끄는 혁신적인 웹개발, 모바일 앱, UI/UX 디자인", + "cta": { + "start": "시작하기", + "portfolio": "포트폴리오 보기" + } + }, + "services": { + "title": { + "our": "우리의", + "services": "서비스" + }, + "subtitle": "아이디어를 현실로 만드는 전문 개발 서비스", + "description": "최첨단 기술과 창의적 아이디어로 완성하는 디지털 솔루션", + "view_all": "모든 서비스 보기", + "web": { + "title": "웹 개발", + "description": "반응형 웹사이트 및 웹 애플리케이션 개발", + "price": "$500부터" + }, + "mobile": { + "title": "모바일 앱", + "description": "iOS 및 Android 네이티브 앱 개발", + "price": "$1,000부터" + }, + "design": { + "title": "UI/UX 디자인", + "description": "사용자 중심의 인터페이스 및 경험 디자인", + "price": "$300부터" + }, + "marketing": { + "title": "디지털 마케팅", + "description": "SEO, 소셜미디어 마케팅, 광고 관리", + "price": "$200부터" + }, + "meta": { + "title": "서비스", + "description": "SmartSolTech의 전문 서비스를 확인해보세요. 웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅 및 기타 기술 솔루션.", + "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅, 기술 솔루션, SmartSolTech" + }, + "hero": { + "title": "우리의", + "title_highlight": "서비스", + "subtitle": "혁신적인 기술로 비즈니스 성장 지원" + }, + "cards": { + "starting_price": "시작 가격", + "consultation": "상담", + "contact": "연락하기", + "calculate_cost": "비용 계산", + "popular": "인기", + "coming_soon": "서비스 준비중", + "coming_soon_desc": "곧 다양한 서비스를 제공할 예정입니다!" + }, + "process": { + "title": "프로젝트 수행 과정", + "subtitle": "체계적이고 전문적인 프로세스로 프로젝트를 진행합니다", + "step1": { + "title": "상담 및 기획", + "description": "고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다" + }, + "step2": { + "title": "디자인 및 설계", + "description": "사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다" + }, + "step3": { + "title": "개발 및 구현", + "description": "최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다" + }, + "step4": { + "title": "테스트 및 배포", + "description": "철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다" + } + }, + "why_choose": { + "title": "왜 SmartSolTech를 선택해야 할까요?", + "modern_tech": { + "title": "최신 기술 활용", + "description": "항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 미래 지향적인 솔루션을 제공합니다." + }, + "expert_team": { + "title": "전문가 팀", + "description": "각 분야의 전문가들로 구성된 팀이 협력하여 최고 품질의 결과물을 보장합니다." + }, + "fast_response": { + "title": "빠른 대응", + "description": "신속한 커뮤니케이션과 효율적인 프로젝트 관리로 정해진 일정 내에 프로젝트를 완료합니다." + }, + "continuous_support": { + "title": "지속적인 지원", + "description": "프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 통해 장기적인 파트너십을 유지합니다." + }, + "quality_guarantee": { + "title": "품질 보장", + "subtitle": "고객 만족을 위한\n최고 품질의 서비스" + } + }, + "cta": { + "title": "프로젝트를 시작할 준비가 되셨나요?", + "subtitle": "무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다", + "free_consultation": "무료 상담 신청", + "calculate_cost": "비용 계산하기", + "view_portfolio": "포트폴리오 보기" + } + }, + "portfolio": { + "title": { + "recent": "최근", + "projects": "프로젝트" + }, + "subtitle": "성공적으로 완료된 프로젝트들을 확인해보세요", + "description": "고객의 성공을 위해 완성한 프로젝트들을 살펴보세요", + "view_details": "자세히 보기", + "view_all": "모든 포트폴리오 보기", + "categories": { + "all": "전체", + "web": "웹 개발", + "mobile": "모바일 앱", + "uiux": "UI/UX 디자인" + }, + "project_details": "프로젝트 상세정보", + "default": { + "ecommerce": "이커머스", + "title": "이커머스 플랫폼", + "description": "직관적인 인터페이스를 가진 현대적인 온라인 커머스 솔루션" + }, + "meta": { + "title": "포트폴리오", + "description": "SmartSolTech의 다양한 프로젝트와 성공 사례들을 확인해보세요. 웹 개발, 모바일 앱, UI/UX 디자인 포트폴리오.", + "keywords": "포트폴리오, 웹 개발, 모바일 앱, UI/UX 디자인, 프로젝트, SmartSolTech" + } + }, + "portfolio_page": { + "title": "우리의 포트폴리오", + "subtitle": "혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요", + "categories": { + "all": "전체", + "web-development": "웹 개발", + "mobile-app": "모바일 앱", + "ui-ux-design": "UI/UX 디자인", + "branding": "브랜딩", + "marketing": "디지털 마케팅" + }, + "buttons": { + "details": "자세히 보기", + "projectDetails": "프로젝트 상세보기", + "loadMore": "더 많은 프로젝트 보기", + "contact": "프로젝트 문의하기", + "calculate": "비용 계산하기" + }, + "empty": { + "title": "아직 포트폴리오가 없습니다", + "subtitle": "곧 멋진 프로젝트들을 공개할 예정입니다!" + }, + "cta": { + "title": "다음 프로젝트의 주인공이 되어보세요", + "subtitle": "우리와 함께 혁신적인 디지털 솔루션을 만들어보세요" + }, + "labels": { + "featured": "추천", + "views": "조회수", + "likes": "좋아요" + } + }, + "calculator": { + "title": "프로젝트 비용 계산기", + "subtitle": "실시간으로 정확한 비용 추정을 받기 위해 원하는 서비스와 요구사항을 선택하세요", + "next_step": "다음 단계", + "prev_step": "이전 단계", + "calculate": "계산하기", + "reset": "초기화", + "meta": { + "title": "프로젝트 비용 계산기", + "description": "대화형 계산기로 웹 개발, 모바일 앱 또는 디자인 프로젝트의 비용을 계산하세요" + }, + "cta": { + "title": "프로젝트 견적을 확인해보세요", + "subtitle": "실시간 비용 계산을 위해 원하는 서비스와 요구사항을 선택하세요", + "button": "비용 계산기 사용하기" + }, + "step1": { + "title": "서비스를 선택하세요", + "nav_title": "단계 1: 서비스", + "subtitle": "프로젝트에 필요한 서비스를 선택하세요" + }, + "step2": { + "title": "프로젝트 세부사항", + "nav_title": "단계 2: 세부사항", + "subtitle": "프로젝트의 복잡도와 일정에 대해 알려주세요" + }, + "complexity": { + "title": "프로젝트 복잡도", + "simple": "간단함", + "simple_desc": "기본 기능 및 표준 기능", + "medium": "보통", + "medium_desc": "맞춤 기능 및 통합", + "complex": "복잡함", + "complex_desc": "고급 기능 및 복잡한 통합" + }, + "timeline": { + "title": "프로젝트 일정", + "standard": "표준 (4-8주)", + "standard_desc": "일반적인 개발 일정", + "rush": "급함 (2-4주)", + "rush_desc": "추가 비용으로 신속 배송", + "extended": "연장 (8주 이상)", + "extended_desc": "비용 최적화와 함께 유연한 일정" + }, + "result": { + "title": "프로젝트 견적", + "subtitle": "예상 프로젝트 비용", + "nav_title": "결과", + "estimated_price": "예상 가격", + "price_note": "이는 대략적인 비용입니다. 최종 가격은 프로젝트 복잡도에 따라 달라질 수 있습니다.", + "summary": "프로젝트 요약", + "get_quote": "상세 견적 받기", + "contact_note": "상세한 상담과 정확한 견적을 위해 저희에게 연락주세요.", + "recalculate": "다시 계산하기", + "selected_services": "선택된 서비스", + "complexity": "복잡도", + "timeline": "일정" + } + }, + "contact": { + "hero": { + "title": "연락하기", + "subtitle": "아이디어를 현실로 만들어드리겠습니다" + }, + "ready_title": "프로젝트를 시작할 준비가 되셨나요?", + "ready_description": "아이디어를 현실로 바꿔보세요. 전문가들이 최고의 솔루션을 제공합니다.", + "form": { + "title": "프로젝트 문의", + "name": "이름", + "email": "이메일", + "phone": "전화번호", + "message": "메시지", + "submit": "문의 보내기", + "success": "문의가 성공적으로 전송되었습니다", + "error": "문의 전송 중 오류가 발생했습니다", + "service": { + "title": "관심 서비스", + "select": "관심 있는 서비스를 선택하세요", + "web": "웹 개발", + "mobile": "모바일 앱", + "design": "UI/UX 디자인", + "branding": "브랜딩", + "consulting": "컨설팅", + "other": "기타" + } + }, + "info": { + "title": "연락처 정보" + }, + "phone": { + "title": "전화 문의", + "number": "+82-2-1234-5678", + "hours": "월-금 9:00-18:00" + }, + "email": { + "title": "이메일 문의", + "address": "info@smartsoltech.co.kr", + "response": "24시간 내 응답" + }, + "telegram": { + "title": "텔레그램", + "subtitle": "빠른 응답을 위해" + }, + "address": { + "title": "사무실 주소", + "line1": "서울시 강남구 테헤란로 123", + "line2": "대한민국" + }, + "cta": { + "ready": "준비되셨나요?", + "start": "시작하기", + "question": "궁금한 점이 있나요?", + "subtitle": "프로젝트에 대한 상담을 제공합니다" + }, + "meta": { + "title": "연락처", + "description": "프로젝트 문의나 상담을 위해 언제든지 연락주세요" + } + }, + "about": { + "hero": { + "title": "SmartSolTech 소개", + "subtitle": "혁신과 기술로 미래를 만들어갑니다" + }, + "company": { + "title": "회사 정보", + "description1": "SmartSolTech는 2020년에 설립된 기술 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야의 전문성으로 인정받고 있습니다.", + "description2": "고객의 니즈를 정확히 파악하고 최신 기술을 활용한 혁신적인 솔루션을 제공합니다." + }, + "stats": { + "projects": "완료된 프로젝트", + "experience": "년 경험", + "clients": "만족한 고객" + }, + "mission": { + "title": "우리의 미션", + "description": "기술을 통해 고객의 비즈니스 성장을 지원하고 디지털 혁신을 선도하는 것이 우리의 미션입니다." + }, + "values": { + "innovation": { + "title": "혁신", + "description": "지속적인 R&D와 최첨단 기술 도입으로 혁신적인 솔루션을 제공합니다." + }, + "quality": { + "title": "품질", + "description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 고품질 제품을 제공합니다." + }, + "partnership": { + "title": "파트너십", + "description": "고객과의 긴밀한 소통과 협력을 통해 최고의 결과를 만들어냅니다." + } + }, + "cta": { + "title": "함께 성장해나가겠습니다", + "subtitle": "아이디어를 현실로 바꿔보세요", + "button": "연락하기" + }, + "meta": { + "title": "회사소개", + "description": "SmartSolTech는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" + } + }, + "footer": { + "description": "혁신을 선도하는 디지털 솔루션 전문가", + "company": { + "description": "혁신을 선도하는 디지털 솔루션 전문가" + }, + "links": { + "title": "빠른 링크" + }, + "contact": { + "title": "연락처", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "서울시 강남구 테헤란로 123" + }, + "copyright": "© {{year}} SmartSolTech. 모든 권리 보유.", + "privacy": "개인정보처리방침", + "terms": "이용약관" + }, + "theme": { + "light": "라이트 테마", + "dark": "다크 테마", + "toggle": "테마 전환" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "로딩 중...", + "error": "오류가 발생했습니다", + "success": "성공", + "view_more": "더 보기", + "back": "뒤로", + "next": "다음", + "previous": "이전", + "view_details": "자세히 보기" + }, + "meta": { + "description": "SmartSolTech - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", + "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", + "title": "SmartSolTech" + }, + "nav": { + "home": "홈", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "calculator": "비용계산기" + }, + "admin": { + "login": "관리자 패널 로그인", + "dashboard": "대시보드", + "title": "SmartSolTech 관리자" + }, + "company": { + "name": "SmartSolTech", + "description": "혁신을 선도하는 디지털 솔루션 전문가", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "페이지를 찾을 수 없습니다", + "error_occurred": "오류가 발생했습니다", + "title": "오류 - SmartSolTech", + "default_title": "오류가 발생했습니다", + "default_message": "요청을 처리하는 중 문제가 발생했습니다.", + "back_home": "홈으로 돌아가기", + "go_back": "뒤로 가기", + "need_help": "도움이 필요하신가요?", + "help_message": "문제가 지속되면 언제든지 저희에게 연락주세요.", + "contact_support": "고객지원 연락하기" + }, + "pages": { + "home": "홈페이지", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "contact": "연락처", + "calculator": "비용계산기" + } +} \ No newline at end of file diff --git a/.history/locales/ko_20251025214837.json b/.history/locales/ko_20251025214837.json new file mode 100644 index 0000000..d408b75 --- /dev/null +++ b/.history/locales/ko_20251025214837.json @@ -0,0 +1,424 @@ +{ + "navigation": { + "home": "홈", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "contact": "연락처", + "calculator": "비용계산기", + "admin": "관리자" + }, + "hero": { + "title": { + "smart": "스마트", + "solutions": "솔루션" + }, + "subtitle": "혁신적인 기술로 비즈니스를 성장시키세요", + "description": "비즈니스의 디지털 전환을 이끄는 혁신적인 웹개발, 모바일 앱, UI/UX 디자인", + "cta": { + "start": "시작하기", + "portfolio": "포트폴리오 보기" + } + }, + "services": { + "title": { + "our": "우리의", + "services": "서비스" + }, + "subtitle": "아이디어를 현실로 만드는 전문 개발 서비스", + "description": "최첨단 기술과 창의적 아이디어로 완성하는 디지털 솔루션", + "view_all": "모든 서비스 보기", + "web": { + "title": "웹 개발", + "description": "반응형 웹사이트 및 웹 애플리케이션 개발", + "price": "$500부터" + }, + "mobile": { + "title": "모바일 앱", + "description": "iOS 및 Android 네이티브 앱 개발", + "price": "$1,000부터" + }, + "design": { + "title": "UI/UX 디자인", + "description": "사용자 중심의 인터페이스 및 경험 디자인", + "price": "$300부터" + }, + "marketing": { + "title": "디지털 마케팅", + "description": "SEO, 소셜미디어 마케팅, 광고 관리", + "price": "$200부터" + }, + "meta": { + "title": "서비스", + "description": "SmartSolTech의 전문 서비스를 확인해보세요. 웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅 및 기타 기술 솔루션.", + "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅, 기술 솔루션, SmartSolTech" + }, + "hero": { + "title": "우리의", + "title_highlight": "서비스", + "subtitle": "혁신적인 기술로 비즈니스 성장 지원" + }, + "cards": { + "starting_price": "시작 가격", + "consultation": "상담", + "contact": "연락하기", + "calculate_cost": "비용 계산", + "popular": "인기", + "coming_soon": "서비스 준비중", + "coming_soon_desc": "곧 다양한 서비스를 제공할 예정입니다!" + }, + "process": { + "title": "프로젝트 수행 과정", + "subtitle": "체계적이고 전문적인 프로세스로 프로젝트를 진행합니다", + "step1": { + "title": "상담 및 기획", + "description": "고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다" + }, + "step2": { + "title": "디자인 및 설계", + "description": "사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다" + }, + "step3": { + "title": "개발 및 구현", + "description": "최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다" + }, + "step4": { + "title": "테스트 및 배포", + "description": "철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다" + } + }, + "why_choose": { + "title": "왜 SmartSolTech를 선택해야 할까요?", + "modern_tech": { + "title": "최신 기술 활용", + "description": "항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 미래 지향적인 솔루션을 제공합니다." + }, + "expert_team": { + "title": "전문가 팀", + "description": "각 분야의 전문가들로 구성된 팀이 협력하여 최고 품질의 결과물을 보장합니다." + }, + "fast_response": { + "title": "빠른 대응", + "description": "신속한 커뮤니케이션과 효율적인 프로젝트 관리로 정해진 일정 내에 프로젝트를 완료합니다." + }, + "continuous_support": { + "title": "지속적인 지원", + "description": "프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 통해 장기적인 파트너십을 유지합니다." + }, + "quality_guarantee": { + "title": "품질 보장", + "subtitle": "고객 만족을 위한\n최고 품질의 서비스" + } + }, + "cta": { + "title": "프로젝트를 시작할 준비가 되셨나요?", + "subtitle": "무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다", + "free_consultation": "무료 상담 신청", + "calculate_cost": "비용 계산하기", + "view_portfolio": "포트폴리오 보기" + } + }, + "portfolio": { + "title": { + "recent": "최근", + "projects": "프로젝트" + }, + "subtitle": "성공적으로 완료된 프로젝트들을 확인해보세요", + "description": "고객의 성공을 위해 완성한 프로젝트들을 살펴보세요", + "view_details": "자세히 보기", + "view_all": "모든 포트폴리오 보기", + "categories": { + "all": "전체", + "web": "웹 개발", + "mobile": "모바일 앱", + "uiux": "UI/UX 디자인" + }, + "project_details": "프로젝트 상세정보", + "default": { + "ecommerce": "이커머스", + "title": "이커머스 플랫폼", + "description": "직관적인 인터페이스를 가진 현대적인 온라인 커머스 솔루션" + }, + "meta": { + "title": "포트폴리오", + "description": "SmartSolTech의 다양한 프로젝트와 성공 사례들을 확인해보세요. 웹 개발, 모바일 앱, UI/UX 디자인 포트폴리오.", + "keywords": "포트폴리오, 웹 개발, 모바일 앱, UI/UX 디자인, 프로젝트, SmartSolTech" + } + }, + "portfolio_page": { + "title": "우리의 포트폴리오", + "subtitle": "혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요", + "categories": { + "all": "전체", + "web-development": "웹 개발", + "mobile-app": "모바일 앱", + "ui-ux-design": "UI/UX 디자인", + "branding": "브랜딩", + "marketing": "디지털 마케팅" + }, + "buttons": { + "details": "자세히 보기", + "projectDetails": "프로젝트 상세보기", + "loadMore": "더 많은 프로젝트 보기", + "contact": "프로젝트 문의하기", + "calculate": "비용 계산하기" + }, + "empty": { + "title": "아직 포트폴리오가 없습니다", + "subtitle": "곧 멋진 프로젝트들을 공개할 예정입니다!" + }, + "cta": { + "title": "다음 프로젝트의 주인공이 되어보세요", + "subtitle": "우리와 함께 혁신적인 디지털 솔루션을 만들어보세요" + }, + "labels": { + "featured": "추천", + "views": "조회수", + "likes": "좋아요" + } + }, + "calculator": { + "title": "프로젝트 비용 계산기", + "subtitle": "실시간으로 정확한 비용 추정을 받기 위해 원하는 서비스와 요구사항을 선택하세요", + "next_step": "다음 단계", + "prev_step": "이전 단계", + "calculate": "계산하기", + "reset": "초기화", + "meta": { + "title": "프로젝트 비용 계산기", + "description": "대화형 계산기로 웹 개발, 모바일 앱 또는 디자인 프로젝트의 비용을 계산하세요" + }, + "cta": { + "title": "프로젝트 견적을 확인해보세요", + "subtitle": "실시간 비용 계산을 위해 원하는 서비스와 요구사항을 선택하세요", + "button": "비용 계산기 사용하기" + }, + "step1": { + "title": "서비스를 선택하세요", + "nav_title": "단계 1: 서비스", + "subtitle": "프로젝트에 필요한 서비스를 선택하세요" + }, + "step2": { + "title": "프로젝트 세부사항", + "nav_title": "단계 2: 세부사항", + "subtitle": "프로젝트의 복잡도와 일정에 대해 알려주세요" + }, + "complexity": { + "title": "프로젝트 복잡도", + "simple": "간단함", + "simple_desc": "기본 기능 및 표준 기능", + "medium": "보통", + "medium_desc": "맞춤 기능 및 통합", + "complex": "복잡함", + "complex_desc": "고급 기능 및 복잡한 통합" + }, + "timeline": { + "title": "프로젝트 일정", + "standard": "표준 (4-8주)", + "standard_desc": "일반적인 개발 일정", + "rush": "급함 (2-4주)", + "rush_desc": "추가 비용으로 신속 배송", + "extended": "연장 (8주 이상)", + "extended_desc": "비용 최적화와 함께 유연한 일정" + }, + "result": { + "title": "프로젝트 견적", + "subtitle": "예상 프로젝트 비용", + "nav_title": "결과", + "estimated_price": "예상 가격", + "price_note": "이는 대략적인 비용입니다. 최종 가격은 프로젝트 복잡도에 따라 달라질 수 있습니다.", + "summary": "프로젝트 요약", + "get_quote": "상세 견적 받기", + "contact_note": "상세한 상담과 정확한 견적을 위해 저희에게 연락주세요.", + "recalculate": "다시 계산하기", + "selected_services": "선택된 서비스", + "complexity": "복잡도", + "timeline": "일정" + } + }, + "contact": { + "hero": { + "title": "연락하기", + "subtitle": "아이디어를 현실로 만들어드리겠습니다" + }, + "ready_title": "프로젝트를 시작할 준비가 되셨나요?", + "ready_description": "아이디어를 현실로 바꿔보세요. 전문가들이 최고의 솔루션을 제공합니다.", + "form": { + "title": "프로젝트 문의", + "name": "이름", + "email": "이메일", + "phone": "전화번호", + "message": "메시지", + "submit": "문의 보내기", + "success": "문의가 성공적으로 전송되었습니다", + "error": "문의 전송 중 오류가 발생했습니다", + "service": { + "title": "관심 서비스", + "select": "관심 있는 서비스를 선택하세요", + "web": "웹 개발", + "mobile": "모바일 앱", + "design": "UI/UX 디자인", + "branding": "브랜딩", + "consulting": "컨설팅", + "other": "기타" + } + }, + "info": { + "title": "연락처 정보" + }, + "phone": { + "title": "전화 문의", + "number": "+82-2-1234-5678", + "hours": "월-금 9:00-18:00" + }, + "email": { + "title": "이메일 문의", + "address": "info@smartsoltech.co.kr", + "response": "24시간 내 응답" + }, + "telegram": { + "title": "텔레그램", + "subtitle": "빠른 응답을 위해" + }, + "address": { + "title": "사무실 주소", + "line1": "서울시 강남구 테헤란로 123", + "line2": "대한민국" + }, + "cta": { + "ready": "준비되셨나요?", + "start": "시작하기", + "question": "궁금한 점이 있나요?", + "subtitle": "프로젝트에 대한 상담을 제공합니다" + }, + "meta": { + "title": "연락처", + "description": "프로젝트 문의나 상담을 위해 언제든지 연락주세요" + } + }, + "about": { + "hero": { + "title": "SmartSolTech 소개", + "subtitle": "혁신과 기술로 미래를 만들어갑니다" + }, + "company": { + "title": "회사 정보", + "description1": "SmartSolTech는 2020년에 설립된 기술 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야의 전문성으로 인정받고 있습니다.", + "description2": "고객의 니즈를 정확히 파악하고 최신 기술을 활용한 혁신적인 솔루션을 제공합니다." + }, + "stats": { + "projects": "완료된 프로젝트", + "experience": "년 경험", + "clients": "만족한 고객" + }, + "mission": { + "title": "우리의 미션", + "description": "기술을 통해 고객의 비즈니스 성장을 지원하고 디지털 혁신을 선도하는 것이 우리의 미션입니다." + }, + "values": { + "innovation": { + "title": "혁신", + "description": "지속적인 R&D와 최첨단 기술 도입으로 혁신적인 솔루션을 제공합니다." + }, + "quality": { + "title": "품질", + "description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 고품질 제품을 제공합니다." + }, + "partnership": { + "title": "파트너십", + "description": "고객과의 긴밀한 소통과 협력을 통해 최고의 결과를 만들어냅니다." + } + }, + "cta": { + "title": "함께 성장해나가겠습니다", + "subtitle": "아이디어를 현실로 바꿔보세요", + "button": "연락하기" + }, + "meta": { + "title": "회사소개", + "description": "SmartSolTech는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" + } + }, + "footer": { + "description": "혁신을 선도하는 디지털 솔루션 전문가", + "company": { + "description": "혁신을 선도하는 디지털 솔루션 전문가" + }, + "links": { + "title": "빠른 링크" + }, + "contact": { + "title": "연락처", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "서울시 강남구 테헤란로 123" + }, + "copyright": "© {{year}} SmartSolTech. 모든 권리 보유.", + "privacy": "개인정보처리방침", + "terms": "이용약관" + }, + "theme": { + "light": "라이트 테마", + "dark": "다크 테마", + "toggle": "테마 전환" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "로딩 중...", + "error": "오류가 발생했습니다", + "success": "성공", + "view_more": "더 보기", + "back": "뒤로", + "next": "다음", + "previous": "이전", + "view_details": "자세히 보기" + }, + "meta": { + "description": "SmartSolTech - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", + "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", + "title": "SmartSolTech" + }, + "nav": { + "home": "홈", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "calculator": "비용계산기" + }, + "admin": { + "login": "관리자 패널 로그인", + "dashboard": "대시보드", + "title": "SmartSolTech 관리자" + }, + "company": { + "name": "SmartSolTech", + "description": "혁신을 선도하는 디지털 솔루션 전문가", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "페이지를 찾을 수 없습니다", + "error_occurred": "오류가 발생했습니다", + "title": "오류 - SmartSolTech", + "default_title": "오류가 발생했습니다", + "default_message": "요청을 처리하는 중 문제가 발생했습니다.", + "back_home": "홈으로 돌아가기", + "go_back": "뒤로 가기", + "need_help": "도움이 필요하신가요?", + "help_message": "문제가 지속되면 언제든지 저희에게 연락주세요.", + "contact_support": "고객지원 연락하기" + }, + "pages": { + "home": "홈페이지", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "contact": "연락처", + "calculator": "비용계산기" + } +} \ No newline at end of file diff --git a/.history/locales/ru_20251019171530.json b/.history/locales/ru_20251019171530.json deleted file mode 100644 index 64cd338..0000000 --- a/.history/locales/ru_20251019171530.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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": "Предыдущий" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251019171645.json b/.history/locales/ru_20251019171645.json deleted file mode 100644 index 64cd338..0000000 --- a/.history/locales/ru_20251019171645.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "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": "Предыдущий" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251019181401.json b/.history/locales/ru_20251019181401.json deleted file mode 100644 index edcfda9..0000000 --- a/.history/locales/ru_20251019181401.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "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": "Предыдущий" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251019181629.json b/.history/locales/ru_20251019181629.json deleted file mode 100644 index edcfda9..0000000 --- a/.history/locales/ru_20251019181629.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "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": "Предыдущий" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251019203653.json b/.history/locales/ru_20251019203653.json deleted file mode 100644 index 56261d2..0000000 --- a/.history/locales/ru_20251019203653.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "hero.title.solutions" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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": "Посмотреть все услуги", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020044247.json b/.history/locales/ru_20251020044247.json deleted file mode 100644 index f75e8b5..0000000 --- a/.history/locales/ru_20251020044247.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "hero.title.solutions" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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": "Посмотреть все услуги", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020044352.json b/.history/locales/ru_20251020044352.json deleted file mode 100644 index f75e8b5..0000000 --- a/.history/locales/ru_20251020044352.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "hero.title.solutions" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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": "Посмотреть все услуги", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020225647.json b/.history/locales/ru_20251020225647.json deleted file mode 100644 index 7d907d3..0000000 --- a/.history/locales/ru_20251020225647.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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": "Посмотреть все услуги", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230125.json b/.history/locales/ru_20251020230125.json deleted file mode 100644 index 5328610..0000000 --- a/.history/locales/ru_20251020230125.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "services.title.our", - "services": "services.title.services" - }, - "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": "Посмотреть все услуги", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230133.json b/.history/locales/ru_20251020230133.json deleted file mode 100644 index c636666..0000000 --- a/.history/locales/ru_20251020230133.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "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" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230144.json b/.history/locales/ru_20251020230144.json deleted file mode 100644 index 5facc8d..0000000 --- a/.history/locales/ru_20251020230144.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230151.json b/.history/locales/ru_20251020230151.json deleted file mode 100644 index 0e75ac0..0000000 --- a/.history/locales/ru_20251020230151.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "portfolio.subtitle" - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230159.json b/.history/locales/ru_20251020230159.json deleted file mode 100644 index 844651b..0000000 --- a/.history/locales/ru_20251020230159.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "contact.form.title", - "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" - }, - "success": "contact.form.success", - "error": "contact.form.error" - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230209.json b/.history/locales/ru_20251020230209.json deleted file mode 100644 index 1420f03..0000000 --- a/.history/locales/ru_20251020230209.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "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" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230219.json b/.history/locales/ru_20251020230219.json deleted file mode 100644 index 0fbd309..0000000 --- a/.history/locales/ru_20251020230219.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "common.view_details" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230226.json b/.history/locales/ru_20251020230226.json deleted file mode 100644 index 045275a..0000000 --- a/.history/locales/ru_20251020230226.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230234.json b/.history/locales/ru_20251020230234.json deleted file mode 100644 index fce0c92..0000000 --- a/.history/locales/ru_20251020230234.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230247.json b/.history/locales/ru_20251020230247.json deleted file mode 100644 index fce0c92..0000000 --- a/.history/locales/ru_20251020230247.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230428.json b/.history/locales/ru_20251020230428.json deleted file mode 100644 index c1dd95d..0000000 --- a/.history/locales/ru_20251020230428.json +++ /dev/null @@ -1,357 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251020230440.json b/.history/locales/ru_20251020230440.json deleted file mode 100644 index c1dd95d..0000000 --- a/.history/locales/ru_20251020230440.json +++ /dev/null @@ -1,357 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021040800.json b/.history/locales/ru_20251021040800.json deleted file mode 100644 index 486afea..0000000 --- a/.history/locales/ru_20251021040800.json +++ /dev/null @@ -1,357 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаунт для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021175542.json b/.history/locales/ru_20251021175542.json deleted file mode 100644 index ef7b41d..0000000 --- a/.history/locales/ru_20251021175542.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021183217.json b/.history/locales/ru_20251021183217.json deleted file mode 100644 index ef7b41d..0000000 --- a/.history/locales/ru_20251021183217.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021183706.json b/.history/locales/ru_20251021183706.json deleted file mode 100644 index 78efed5..0000000 --- a/.history/locales/ru_20251021183706.json +++ /dev/null @@ -1,402 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - }, - "portfolio": { - "default": { - "ecommerce": "Электронная коммерция", - "title": "Интернет-магазин", - "description": "Современная платформа электронной коммерции с интуитивным интерфейсом" - } - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021183802.json b/.history/locales/ru_20251021183802.json deleted file mode 100644 index b28e6c4..0000000 --- a/.history/locales/ru_20251021183802.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184019.json b/.history/locales/ru_20251021184019.json deleted file mode 100644 index 788e599..0000000 --- a/.history/locales/ru_20251021184019.json +++ /dev/null @@ -1,403 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184027.json b/.history/locales/ru_20251021184027.json deleted file mode 100644 index 788e599..0000000 --- a/.history/locales/ru_20251021184027.json +++ /dev/null @@ -1,403 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184143.json b/.history/locales/ru_20251021184143.json deleted file mode 100644 index f60ff92..0000000 --- a/.history/locales/ru_20251021184143.json +++ /dev/null @@ -1,408 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184308.json b/.history/locales/ru_20251021184308.json deleted file mode 100644 index f60ff92..0000000 --- a/.history/locales/ru_20251021184308.json +++ /dev/null @@ -1,408 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184331.json b/.history/locales/ru_20251021184331.json deleted file mode 100644 index 9634358..0000000 --- a/.history/locales/ru_20251021184331.json +++ /dev/null @@ -1,430 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184333.json b/.history/locales/ru_20251021184333.json deleted file mode 100644 index 9634358..0000000 --- a/.history/locales/ru_20251021184333.json +++ /dev/null @@ -1,430 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184528.json b/.history/locales/ru_20251021184528.json deleted file mode 100644 index c4e67e7..0000000 --- a/.history/locales/ru_20251021184528.json +++ /dev/null @@ -1,437 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021184547.json b/.history/locales/ru_20251021184547.json deleted file mode 100644 index c4e67e7..0000000 --- a/.history/locales/ru_20251021184547.json +++ /dev/null @@ -1,437 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - } - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "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" - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021210015.json b/.history/locales/ru_20251021210015.json deleted file mode 100644 index 3608079..0000000 --- a/.history/locales/ru_20251021210015.json +++ /dev/null @@ -1,484 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_project": "View Project", - "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" - }, - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - } - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": "Посмотреть портфолио", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "links": { - "title": "footer.links.title", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "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", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021210040.json b/.history/locales/ru_20251021210040.json deleted file mode 100644 index 753c566..0000000 --- a/.history/locales/ru_20251021210040.json +++ /dev/null @@ -1,485 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_project": "View Project", - "categories": { - "all": "Все", - "web": "Веб-разработка", - "mobile": "Мобильные приложения", - "uiux": "UI/UX дизайн" - }, - "project_details": "Детали проекта", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - } - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": "Посмотреть портфолио", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "links": { - "title": "footer.links.title", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "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", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021210347.json b/.history/locales/ru_20251021210347.json deleted file mode 100644 index 34141a4..0000000 --- a/.history/locales/ru_20251021210347.json +++ /dev/null @@ -1,485 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - } - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "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": "Посмотреть все услуги", - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "title_highlight": "Проекты", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_project": "View Project", - "categories": { - "all": "Все", - "web": "Веб-разработка", - "mobile": "Мобильные приложения", - "uiux": "UI/UX дизайн" - }, - "project_details": "Детали проекта", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - } - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "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": "Подать заявку на консультацию", - "title": "Заявка на бесплатную консультацию", - "service": { - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "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": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - }, - "partnership": { - "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": "Посмотреть портфолио", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - } - }, - "footer": { - "company": { - "description": "footer.company.description" - }, - "description": "Специалист по цифровым решениям, ведущий инновации", - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "links": { - "title": "footer.links.title", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" - }, - "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", - "social": { - "follow": "Follow Us" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech", - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard": "Панель управления", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "title": "SmartSolTech Admin", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "full_name": "SmartSolTech - Инновационные технологические решения", - "description": "Специалист по цифровым решениям, ведущий инновации", - "tagline": "Будущее начинается здесь", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "contact_us": "Свяжитесь с нами", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - } -} \ No newline at end of file diff --git a/.history/locales/ru_20251021210903.json b/.history/locales/ru_20251021210903.json deleted file mode 100644 index c8f4b9f..0000000 --- a/.history/locales/ru_20251021210903.json +++ /dev/null @@ -1,492 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - }, - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио" - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "description": "Цифровые решения с использованием передовых технологий и творческих идей", - "view_all": "Посмотреть все услуги", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - }, - "title_highlight": "Услуги", - "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~" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "categories": { - "all": "Все", - "web": "Веб-разработка", - "mobile": "Мобильные приложения", - "uiux": "UI/UX дизайн" - }, - "project_details": "Детали проекта", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - }, - "title_highlight": "Проекты", - "view_project": "View Project" - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "ready_title": "Готовы начать свой проект?", - "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "form": { - "title": "Заявка на проект", - "name": "Имя", - "email": "Электронная почта", - "phone": "Телефон", - "message": "Кратко опишите ваш проект", - "submit": "Отправить заявку", - "success": "Заявка успешно отправлена", - "error": "Произошла ошибка при отправке заявки", - "service": { - "title": "Интересующая услуга", - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - } - }, - "info": { - "title": "Контактная информация" - }, - "service_interest": "Интересующая услуга", - "service_options": { - "select": "Выберите интересующую услугу", - "web_development": "Веб-разработка", - "mobile_app": "Мобильное приложение", - "ui_ux_design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-10-1234-5678" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.kr" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Мгновенный ответ доступен" - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "phone_consultation": "Телефонная консультация", - "email_inquiry": "Запрос по электронной почте", - "telegram_chat": "Чат в Telegram", - "instant_response": "Мгновенный ответ доступен", - "free_consultation": "Заявка на бесплатную консультацию" - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Инновации", - "description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "partnership": { - "title": "Сотрудничество", - "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." - }, - "title": "Основные", - "title_highlight": "Ценности", - "description": "Основные ценности, которых придерживается SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - } - }, - "cta": { - "title": "Станьте партнером для совместного успеха", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "button": "Связаться с нами", - "description": "Выведите свой бизнес на следующий уровень с SmartSolTech", - "partnership": "Запрос о партнерстве", - "portfolio": "Посмотреть портфолио" - }, - "meta": { - "title": "О нас", - "description": "SmartSolTech - это профессиональная компания по разработке, которая поддерживает рост бизнеса клиентов с помощью инновационных технологий" - }, - "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": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру" - }, - "team": { - "title": "Наша", - "title_highlight": "Команда", - "description": "Представляем команду SmartSolTech с экспертизой и страстью" - }, - "tech_stack": { - "title": "Технологический", - "title_highlight": "Стек", - "description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами", - "frontend": "Frontend", - "backend": "Backend", - "mobile": "Мобильные" - } - }, - "footer": { - "description": "Специалист по цифровым решениям, ведущий инновации", - "links": { - "title": "footer.links.title", - "privacy": "Политика конфиденциальности", - "terms": "Условия обслуживания", - "sitemap": "Карта сайта" - }, - "contact": { - "title": "Контакты", - "email": "E-mail", - "phone": "Телефон", - "address": "Адрес" - }, - "copyright": "© 2024 SmartSolTech. Все права защищены.", - "company": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса" - }, - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "privacy": "footer.privacy", - "terms": "footer.terms", - "social": { - "follow": "Подписывайтесь на нас" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "dashboard": "Панель управления", - "title": "SmartSolTech Admin", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Специалист по цифровым решениям, ведущий инновации", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Инновационные технологические решения", - "tagline": "Будущее начинается здесь", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ru_20251021210932.json b/.history/locales/ru_20251021210932.json deleted file mode 100644 index 75b1b2d..0000000 --- a/.history/locales/ru_20251021210932.json +++ /dev/null @@ -1,499 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - }, - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио" - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "description": "Цифровые решения с использованием передовых технологий и творческих идей", - "view_all": "Посмотреть все услуги", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - }, - "title_highlight": "Услуги", - "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~" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "categories": { - "all": "Все", - "web": "Веб-разработка", - "mobile": "Мобильные приложения", - "uiux": "UI/UX дизайн" - }, - "project_details": "Детали проекта", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - }, - "title_highlight": "Проекты", - "view_project": "View Project" - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "ready_title": "Готовы начать свой проект?", - "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "form": { - "title": "Заявка на проект", - "name": "Имя", - "email": "Электронная почта", - "phone": "Телефон", - "message": "Кратко опишите ваш проект", - "submit": "Отправить заявку", - "success": "Заявка успешно отправлена", - "error": "Произошла ошибка при отправке заявки", - "service": { - "title": "Интересующая услуга", - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - } - }, - "info": { - "title": "Контактная информация" - }, - "service_interest": "Интересующая услуга", - "service_options": { - "select": "Выберите интересующую услугу", - "web_development": "Веб-разработка", - "mobile_app": "Мобильное приложение", - "ui_ux_design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-2-1234-5678", - "hours": "Пн-Пт 9:00-18:00" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.co.kr", - "response": "Ответ в течение 24 часов" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Для быстрого ответа" - }, - "address": { - "title": "Адрес офиса", - "line1": "Теheran-ro 123, Gangnam-gu", - "line2": "Сеул, Южная Корея" - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "phone_consultation": "Телефонная консультация", - "email_inquiry": "Запрос по электронной почте", - "telegram_chat": "Чат в Telegram", - "instant_response": "Мгновенный ответ доступен", - "free_consultation": "Заявка на бесплатную консультацию" - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Инновации", - "description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "partnership": { - "title": "Сотрудничество", - "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." - }, - "title": "Основные", - "title_highlight": "Ценности", - "description": "Основные ценности, которых придерживается SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - } - }, - "cta": { - "title": "Станьте партнером для совместного успеха", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "button": "Связаться с нами", - "description": "Выведите свой бизнес на следующий уровень с SmartSolTech", - "partnership": "Запрос о партнерстве", - "portfolio": "Посмотреть портфолио" - }, - "meta": { - "title": "О нас", - "description": "SmartSolTech - это профессиональная компания по разработке, которая поддерживает рост бизнеса клиентов с помощью инновационных технологий" - }, - "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": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру" - }, - "team": { - "title": "Наша", - "title_highlight": "Команда", - "description": "Представляем команду SmartSolTech с экспертизой и страстью" - }, - "tech_stack": { - "title": "Технологический", - "title_highlight": "Стек", - "description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами", - "frontend": "Frontend", - "backend": "Backend", - "mobile": "Мобильные" - } - }, - "footer": { - "description": "Специалист по цифровым решениям, ведущий инновации", - "links": { - "title": "footer.links.title", - "privacy": "Политика конфиденциальности", - "terms": "Условия обслуживания", - "sitemap": "Карта сайта" - }, - "contact": { - "title": "Контакты", - "email": "E-mail", - "phone": "Телефон", - "address": "Адрес" - }, - "copyright": "© 2024 SmartSolTech. Все права защищены.", - "company": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса" - }, - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "privacy": "footer.privacy", - "terms": "footer.terms", - "social": { - "follow": "Подписывайтесь на нас" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "dashboard": "Панель управления", - "title": "SmartSolTech Admin", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Специалист по цифровым решениям, ведущий инновации", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Инновационные технологические решения", - "tagline": "Будущее начинается здесь", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ru_20251021211322.json b/.history/locales/ru_20251021211322.json deleted file mode 100644 index ad76eca..0000000 --- a/.history/locales/ru_20251021211322.json +++ /dev/null @@ -1,531 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - }, - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио" - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "description": "Цифровые решения с использованием передовых технологий и творческих идей", - "view_all": "Посмотреть все услуги", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - }, - "title_highlight": "Услуги", - "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~" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "categories": { - "all": "Все", - "web": "Веб-разработка", - "mobile": "Мобильные приложения", - "uiux": "UI/UX дизайн" - }, - "project_details": "Детали проекта", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - }, - "title_highlight": "Проекты", - "view_project": "View Project" - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "ready_title": "Готовы начать свой проект?", - "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "form": { - "title": "Заявка на проект", - "name": "Имя", - "email": "Электронная почта", - "phone": "Телефон", - "message": "Кратко опишите ваш проект", - "submit": "Отправить заявку", - "success": "Заявка успешно отправлена", - "error": "Произошла ошибка при отправке заявки", - "service": { - "title": "Интересующая услуга", - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - } - }, - "info": { - "title": "Контактная информация" - }, - "service_interest": "Интересующая услуга", - "service_options": { - "select": "Выберите интересующую услугу", - "web_development": "Веб-разработка", - "mobile_app": "Мобильное приложение", - "ui_ux_design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-2-1234-5678", - "hours": "Пн-Пт 9:00-18:00" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.co.kr", - "response": "Ответ в течение 24 часов" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Для быстрого ответа" - }, - "address": { - "title": "Адрес офиса", - "line1": "Теheran-ro 123, Gangnam-gu", - "line2": "Сеул, Южная Корея" - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "phone_consultation": "Телефонная консультация", - "email_inquiry": "Запрос по электронной почте", - "telegram_chat": "Чат в Telegram", - "instant_response": "Мгновенный ответ доступен", - "free_consultation": "Заявка на бесплатную консультацию" - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Инновации", - "description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "partnership": { - "title": "Сотрудничество", - "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." - }, - "title": "Основные", - "title_highlight": "Ценности", - "description": "Основные ценности, которых придерживается SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - } - }, - "cta": { - "title": "Станьте партнером для совместного успеха", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "button": "Связаться с нами", - "description": "Выведите свой бизнес на следующий уровень с SmartSolTech", - "partnership": "Запрос о партнерстве", - "portfolio": "Посмотреть портфолио" - }, - "meta": { - "title": "О нас", - "description": "SmartSolTech - это профессиональная компания по разработке, которая поддерживает рост бизнеса клиентов с помощью инновационных технологий" - }, - "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": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру" - }, - "team": { - "title": "Наша", - "title_highlight": "Команда", - "description": "Представляем команду SmartSolTech с экспертизой и страстью" - }, - "tech_stack": { - "title": "Технологический", - "title_highlight": "Стек", - "description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами", - "frontend": "Frontend", - "backend": "Backend", - "mobile": "Мобильные" - } - }, - "footer": { - "description": "Специалист по цифровым решениям, ведущий инновации", - "links": { - "title": "footer.links.title", - "privacy": "Политика конфиденциальности", - "terms": "Условия обслуживания", - "sitemap": "Карта сайта" - }, - "contact": { - "title": "Контакты", - "email": "E-mail", - "phone": "Телефон", - "address": "Адрес" - }, - "copyright": "© 2024 SmartSolTech. Все права защищены.", - "company": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса" - }, - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "privacy": "footer.privacy", - "terms": "footer.terms", - "social": { - "follow": "Подписывайтесь на нас" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "dashboard": "Панель управления", - "title": "SmartSolTech Admin", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Специалист по цифровым решениям, ведущий инновации", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Инновационные технологические решения", - "tagline": "Будущее начинается здесь", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - }, - "portfolio_page": { - "title": "Наше портфолио", - "subtitle": "Знакомьтесь с инновационными проектами и креативными решениями", - "categories": { - "all": "Все", - "web-development": "Веб-разработка", - "mobile-app": "Мобильные приложения", - "ui-ux-design": "UI/UX дизайн", - "branding": "Брендинг", - "marketing": "Цифровой маркетинг" - }, - "buttons": { - "details": "Подробнее", - "projectDetails": "Подробности проекта", - "loadMore": "Больше проектов", - "contact": "Запросить проект", - "calculate": "Рассчитать стоимость" - }, - "empty": { - "title": "Портфолио пока пусто", - "subtitle": "Скоро мы представим замечательные проекты!" - }, - "cta": { - "title": "Станьте героем следующего проекта", - "subtitle": "Создайте с нами инновационное цифровое решение" - }, - "labels": { - "featured": "РЕКОМЕНДУЕМОЕ", - "views": "просмотров", - "likes": "лайков" - } - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ru_20251021211513.json b/.history/locales/ru_20251021211513.json deleted file mode 100644 index ad76eca..0000000 --- a/.history/locales/ru_20251021211513.json +++ /dev/null @@ -1,531 +0,0 @@ -{ - "navigation": { - "home": "Главная", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "Главная - SmartSolTech" - }, - "hero": { - "title": { - "smart": "SmartSolTech", - "solutions": "Future begins here" - }, - "subtitle": "Решения", - "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", - "cta": { - "start": "Начать проект", - "portfolio": "Посмотреть портфолио" - }, - "cta_primary": "Начать проект", - "cta_secondary": "Посмотреть портфолио" - }, - "services": { - "title": { - "our": "Наши", - "services": "Услуги" - }, - "subtitle": "Цифровые решения с использованием передовых технологий и творческих идей", - "description": "Цифровые решения с использованием передовых технологий и творческих идей", - "view_all": "Посмотреть все услуги", - "web": { - "title": "Веб-разработка", - "description": "Современные и адаптивные веб-сайты и веб-приложения", - "price": "от $5,000" - }, - "mobile": { - "title": "Мобильные приложения", - "description": "Нативные и кроссплатформенные приложения для iOS и Android", - "price": "от $8,000" - }, - "design": { - "title": "UI/UX дизайн", - "description": "Ориентированный на пользователя интуитивный и красивый дизайн интерфейса", - "price": "от $3,000" - }, - "marketing": { - "title": "Цифровой маркетинг", - "description": "Цифровой маркетинг через SEO, социальные сети, онлайн-рекламу", - "price": "от $2,000" - }, - "meta": { - "title": "Услуги", - "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" - }, - "hero": { - "title": "Наши", - "title_highlight": "Услуги", - "subtitle": "Поддерживаем рост бизнеса с помощью инновационных технологий" - }, - "cards": { - "starting_price": "Стартовая цена", - "consultation": "консультация", - "contact": "Связаться", - "calculate_cost": "Рассчитать стоимость", - "popular": "Популярно", - "coming_soon": "Услуги готовятся", - "coming_soon_desc": "Скоро мы предложим разнообразные услуги!" - }, - "process": { - "title": "Процесс реализации проекта", - "subtitle": "Мы ведем проекты с помощью систематических и профессиональных процессов", - "consultation": { - "title": "Консультация и планирование", - "description": "Точно понимаем требования клиента и" - } - }, - "title_highlight": "Услуги", - "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~" - } - }, - "portfolio": { - "title": { - "recent": "Последние", - "projects": "Проекты", - "our": "Our", - "portfolio": "Portfolio" - }, - "subtitle": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "description": "Ознакомьтесь с проектами, выполненными для успеха клиентов", - "view_details": "Подробнее", - "view_all": "Посмотреть все портфолио", - "categories": { - "all": "Все", - "web": "Веб-разработка", - "mobile": "Мобильные приложения", - "uiux": "UI/UX дизайн" - }, - "project_details": "Детали проекта", - "default": { - "ecommerce": "Электронная коммерция", - "title": "Платформа электронной коммерции", - "description": "Современное решение для онлайн-торговли с интуитивным интерфейсом" - }, - "meta": { - "title": "Портфолио", - "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", - "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech", - "og_title": "Портфолио - SmartSolTech", - "og_description": "Разнообразные проекты и истории успеха SmartSolTech" - }, - "title_highlight": "Проекты", - "view_project": "View Project" - }, - "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": { - "hero": { - "title": "Свяжитесь с нами", - "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" - }, - "ready_title": "Готовы начать свой проект?", - "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "form": { - "title": "Заявка на проект", - "name": "Имя", - "email": "Электронная почта", - "phone": "Телефон", - "message": "Кратко опишите ваш проект", - "submit": "Отправить заявку", - "success": "Заявка успешно отправлена", - "error": "Произошла ошибка при отправке заявки", - "service": { - "title": "Интересующая услуга", - "select": "Выберите интересующую услугу", - "web": "Веб-разработка", - "mobile": "Мобильное приложение", - "design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - } - }, - "info": { - "title": "Контактная информация" - }, - "service_interest": "Интересующая услуга", - "service_options": { - "select": "Выберите интересующую услугу", - "web_development": "Веб-разработка", - "mobile_app": "Мобильное приложение", - "ui_ux_design": "UI/UX дизайн", - "branding": "Брендинг", - "consulting": "Консалтинг", - "other": "Другое" - }, - "success": "Спасибо! Мы свяжемся с вами в ближайшее время.", - "error": "Произошла ошибка. Попробуйте снова." - }, - "phone": { - "title": "Телефонная консультация", - "number": "+82-2-1234-5678", - "hours": "Пн-Пт 9:00-18:00" - }, - "email": { - "title": "Электронная почта", - "address": "info@smartsoltech.co.kr", - "response": "Ответ в течение 24 часов" - }, - "telegram": { - "title": "Telegram", - "subtitle": "Для быстрого ответа" - }, - "address": { - "title": "Адрес офиса", - "line1": "Теheran-ro 123, Gangnam-gu", - "line2": "Сеул, Южная Корея" - }, - "cta": { - "ready": "Готовы начать", - "start": "свой проект", - "question": "?", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения." - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "phone_consultation": "Телефонная консультация", - "email_inquiry": "Запрос по электронной почте", - "telegram_chat": "Чат в Telegram", - "instant_response": "Мгновенный ответ доступен", - "free_consultation": "Заявка на бесплатную консультацию" - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Инновации", - "description": "Мы предоставляем инновационные решения через непрерывные исследования и внедрение передовых технологий." - }, - "quality": { - "title": "Качество", - "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." - }, - "partnership": { - "title": "Сотрудничество", - "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." - }, - "title": "Основные", - "title_highlight": "Ценности", - "description": "Основные ценности, которых придерживается SmartSolTech", - "collaboration": { - "title": "Collaboration", - "description": "We create the best results through close communication and collaboration with customers." - }, - "growth": { - "title": "Рост", - "description": "Мы растем вместе с клиентами и стремимся к непрерывному обучению и развитию." - } - }, - "cta": { - "title": "Станьте партнером для совместного успеха", - "subtitle": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", - "button": "Связаться с нами", - "description": "Выведите свой бизнес на следующий уровень с SmartSolTech", - "partnership": "Запрос о партнерстве", - "portfolio": "Посмотреть портфолио" - }, - "meta": { - "title": "О нас", - "description": "SmartSolTech - это профессиональная компания по разработке, которая поддерживает рост бизнеса клиентов с помощью инновационных технологий" - }, - "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": "Рост как глобальной компании цифровых решений, представляющей Корею, для ведения цифровых инноваций для клиентов по всему миру" - }, - "team": { - "title": "Наша", - "title_highlight": "Команда", - "description": "Представляем команду SmartSolTech с экспертизой и страстью" - }, - "tech_stack": { - "title": "Технологический", - "title_highlight": "Стек", - "description": "Мы предоставляем лучшие решения с передовыми технологиями и проверенными инструментами", - "frontend": "Frontend", - "backend": "Backend", - "mobile": "Мобильные" - } - }, - "footer": { - "description": "Специалист по цифровым решениям, ведущий инновации", - "links": { - "title": "footer.links.title", - "privacy": "Политика конфиденциальности", - "terms": "Условия обслуживания", - "sitemap": "Карта сайта" - }, - "contact": { - "title": "Контакты", - "email": "E-mail", - "phone": "Телефон", - "address": "Адрес" - }, - "copyright": "© 2024 SmartSolTech. Все права защищены.", - "company": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса" - }, - "quick_links": "Быстрые ссылки", - "services": "Услуги", - "contact_info": "Контактная информация", - "follow_us": "Подписывайтесь", - "rights": "Все права защищены.", - "privacy": "footer.privacy", - "terms": "footer.terms", - "social": { - "follow": "Подписывайтесь на нас" - } - }, - "theme": { - "light": "Светлая тема", - "dark": "Темная тема", - "toggle": "Переключить тему" - }, - "language": { - "english": "English", - "korean": "한국어", - "russian": "Русский", - "kazakh": "Қазақша", - "ko": "한국어", - "en": "English", - "ru": "Русский", - "kk": "Қазақша" - }, - "common": { - "loading": "Загрузка...", - "error": "Произошла ошибка", - "success": "Успешно", - "view_more": "Посмотреть еще", - "back": "Назад", - "next": "Далее", - "previous": "Предыдущий", - "view_details": "Подробнее" - }, - "meta": { - "description": "SmartSolTech - Инновационные технологические решения для вашего бизнеса", - "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, SmartSolTech", - "title": "SmartSolTech - Инновационные технологические решения" - }, - "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" - }, - "admin": { - "login": "Вход в админ панель", - "dashboard": "Панель управления", - "title": "SmartSolTech Admin", - "login_title": "Вход в админ панель", - "login_subtitle": "Войдите в свой аккаunt для управления сайтом", - "login_button": "Войти", - "email": "Email", - "password": "Пароль", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Введите пароль", - "back_to_site": "Вернуться на сайт", - "dashboard_subtitle": "Обзор основных показателей сайта", - "portfolio": "Портфолио", - "services": "Услуги", - "contacts": "Сообщения", - "settings": "Настройки", - "users": "Пользователи", - "logout": "Выход", - "view_site": "Посмотреть сайт", - "view_all": "Посмотреть всё", - "portfolio_projects": "Проекты", - "contact_messages": "Сообщения", - "recent_portfolio": "Последние проекты", - "recent_contacts": "Последние сообщения", - "no_recent_portfolio": "Нет недавних проектов", - "no_recent_contacts": "Нет недавних сообщений", - "quick_actions": "Быстрые действия", - "add_portfolio": "Добавить проект", - "add_service": "Добавить услугу", - "site_settings": "Настройки сайта", - "banner_editor": "Редактор Баннеров", - "current_banner": "Текущий баннер", - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио" - } - }, - "company": { - "name": "SmartSolTech", - "description": "Специалист по цифровым решениям, ведущий инновации", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Инновационные технологические решения", - "tagline": "Будущее начинается здесь", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } - }, - "errors": { - "page_not_found": "Страница не найдена", - "error_occurred": "Произошла ошибка", - "title": "Произошла ошибка - SmartSolTech", - "default_title": "Произошла ошибка", - "default_message": "При обработке запроса возникла проблема.", - "back_home": "Вернуться на главную", - "go_back": "Назад", - "need_help": "Нужна помощь?", - "help_message": "Если проблема продолжается, свяжитесь с нами в любое время.", - "contact_support": "Обратиться в поддержку", - "contact_us": "Свяжитесь с нами" - }, - "pages": { - "home": "Главная страница", - "about": "О нас", - "services": "Услуги", - "portfolio": "Портфолио", - "contact": "Контакты", - "calculator": "Калькулятор" - }, - "portfolio_page": { - "title": "Наше портфолио", - "subtitle": "Знакомьтесь с инновационными проектами и креативными решениями", - "categories": { - "all": "Все", - "web-development": "Веб-разработка", - "mobile-app": "Мобильные приложения", - "ui-ux-design": "UI/UX дизайн", - "branding": "Брендинг", - "marketing": "Цифровой маркетинг" - }, - "buttons": { - "details": "Подробнее", - "projectDetails": "Подробности проекта", - "loadMore": "Больше проектов", - "contact": "Запросить проект", - "calculate": "Рассчитать стоимость" - }, - "empty": { - "title": "Портфолио пока пусто", - "subtitle": "Скоро мы представим замечательные проекты!" - }, - "cta": { - "title": "Станьте героем следующего проекта", - "subtitle": "Создайте с нами инновационное цифровое решение" - }, - "labels": { - "featured": "РЕКОМЕНДУЕМОЕ", - "views": "просмотров", - "likes": "лайков" - } - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" -} \ No newline at end of file diff --git a/.history/locales/ru_20251025214839.json b/.history/locales/ru_20251025214839.json new file mode 100644 index 0000000..17c2132 --- /dev/null +++ b/.history/locales/ru_20251025214839.json @@ -0,0 +1,424 @@ +{ + "navigation": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор", + "admin": "Админ" + }, + "hero": { + "title": { + "smart": "Умные", + "solutions": "Решения" + }, + "subtitle": "Развивайте свой бизнес с инновационными технологиями", + "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", + "cta": { + "start": "Начать", + "portfolio": "Смотреть портфолио" + } + }, + "services": { + "title": { + "our": "Наши", + "services": "Услуги" + }, + "subtitle": "Профессиональные услуги разработки для воплощения ваших идей в реальность", + "description": "Цифровые решения с использованием передовых технологий и творческих идей", + "view_all": "Посмотреть все услуги", + "web": { + "title": "Веб-разработка", + "description": "Адаптивные сайты и веб-приложения", + "price": "От $500" + }, + "mobile": { + "title": "Мобильные приложения", + "description": "Разработка нативных приложений для iOS и Android", + "price": "От $1,000" + }, + "design": { + "title": "UI/UX Дизайн", + "description": "Дизайн интерфейсов и пользовательского опыта", + "price": "От $300" + }, + "marketing": { + "title": "Цифровой маркетинг", + "description": "SEO, SMM, управление рекламой", + "price": "От $200" + }, + "meta": { + "title": "Услуги", + "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" + }, + "hero": { + "title": "Наши", + "title_highlight": "Услуги", + "subtitle": "Поддержка роста бизнеса с инновационными технологиями" + }, + "cards": { + "starting_price": "Начальная цена", + "consultation": "консультация", + "contact": "Связаться", + "calculate_cost": "Рассчитать стоимость", + "popular": "Популярно", + "coming_soon": "Услуги скоро появятся", + "coming_soon_desc": "Скоро мы предложим различные услуги!" + }, + "process": { + "title": "Процесс реализации проекта", + "subtitle": "Мы ведем проекты с системным и профессиональным подходом", + "step1": { + "title": "Консультация и планирование", + "description": "Точно понимаем требования клиента и планируем оптимальное решение" + }, + "step2": { + "title": "Дизайн и проектирование", + "description": "Проектируем интуитивный дизайн, ориентированный на пользователя, и надежную системную архитектуру" + }, + "step3": { + "title": "Разработка и реализация", + "description": "Разрабатываем эффективные и масштабируемые решения, используя новейшие технологии и лучшие практики" + }, + "step4": { + "title": "Тестирование и развертывание", + "description": "Обеспечиваем качество через тщательное тестирование и проводим стабильное развертывание" + } + }, + "why_choose": { + "title": "Почему стоит выбрать SmartSolTech?", + "modern_tech": { + "title": "Использование современных технологий", + "description": "Всегда отслеживаем новейшие технологические тренды и используем проверенный технологический стек для предоставления футуристических решений." + }, + "expert_team": { + "title": "Экспертная команда", + "description": "Команда, состоящая из экспертов в каждой области, сотрудничает для обеспечения результатов высочайшего качества." + }, + "fast_response": { + "title": "Быстрое реагирование", + "description": "Завершаем проекты в установленные сроки благодаря быстрой коммуникации и эффективному управлению проектами." + }, + "continuous_support": { + "title": "Постоянная поддержка", + "description": "Поддерживаем долгосрочное партнерство через постоянное обслуживание и техническую поддержку даже после завершения проекта." + }, + "quality_guarantee": { + "title": "Гарантия качества", + "subtitle": "Для удовлетворения клиентов\nСервис высочайшего качества" + } + }, + "cta": { + "title": "Готовы начать проект?", + "subtitle": "Мы предложим оптимальное решение через бесплатную консультацию", + "free_consultation": "Подать заявку на бесплатную консультацию", + "calculate_cost": "Рассчитать стоимость", + "view_portfolio": "Посмотреть портфолио" + } + }, + "portfolio": { + "title": { + "recent": "Недавние", + "projects": "Проекты" + }, + "subtitle": "Ознакомьтесь с успешно завершенными проектами", + "description": "Посмотрите на проекты, выполненные для успеха клиентов", + "view_details": "Подробнее", + "view_all": "Посмотреть все портфолио", + "categories": { + "all": "Все", + "web": "Веб-разработка", + "mobile": "Мобильные приложения", + "uiux": "UI/UX Дизайн" + }, + "project_details": "Детали проекта", + "default": { + "ecommerce": "Электронная коммерция", + "title": "Платформа электронной коммерции", + "description": "Современное решение для интернет-торговли с интуитивным интерфейсом" + }, + "meta": { + "title": "Портфолио", + "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", + "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Наше портфолио", + "subtitle": "Откройте для себя инновационные проекты и креативные решения", + "categories": { + "all": "Все", + "web-development": "Веб-разработка", + "mobile-app": "Мобильные приложения", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифровой маркетинг" + }, + "buttons": { + "details": "Подробнее", + "projectDetails": "Детали проекта", + "loadMore": "Загрузить больше проектов", + "contact": "Заказать проект", + "calculate": "Рассчитать стоимость" + }, + "empty": { + "title": "Портфолио пока пусто", + "subtitle": "Скоро мы представим потрясающие проекты!" + }, + "cta": { + "title": "Станьте звездой следующего проекта", + "subtitle": "Создавайте инновационные цифровые решения вместе с нами" + }, + "labels": { + "featured": "РЕКОМЕНДУЕМОЕ", + "views": "просмотров", + "likes": "лайков" + } + }, + "calculator": { + "title": "Калькулятор стоимости проекта", + "subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в реальном времени", + "next_step": "Следующий шаг", + "prev_step": "Предыдущий шаг", + "calculate": "Рассчитать", + "reset": "Сброс", + "meta": { + "title": "Калькулятор стоимости проекта", + "description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора" + }, + "cta": { + "title": "Проверьте оценку вашего проекта", + "subtitle": "Выберите нужные услуги и требования для расчета стоимости в реальном времени", + "button": "Использовать калькулятор стоимости" + }, + "step1": { + "title": "Выберите ваши услуги", + "nav_title": "Шаг 1: Услуги", + "subtitle": "Выберите услуги, необходимые для вашего проекта" + }, + "step2": { + "title": "Детали проекта", + "nav_title": "Шаг 2: Детали", + "subtitle": "Расскажите нам о сложности и сроках вашего проекта" + }, + "complexity": { + "title": "Сложность проекта", + "simple": "Простой", + "simple_desc": "Базовые функции и стандартный функционал", + "medium": "Средний", + "medium_desc": "Пользовательские функции и интеграции", + "complex": "Сложный", + "complex_desc": "Продвинутые функции и сложные интеграции" + }, + "timeline": { + "title": "Сроки проекта", + "standard": "Стандартные (4-8 недель)", + "standard_desc": "Обычные сроки разработки", + "rush": "Срочные (2-4 недели)", + "rush_desc": "Ускоренная доставка с дополнительной стоимостью", + "extended": "Расширенные (8+ недель)", + "extended_desc": "Гибкие сроки с оптимизацией стоимости" + }, + "result": { + "title": "Оценка проекта", + "subtitle": "Ваша расчетная стоимость проекта", + "nav_title": "Результат", + "estimated_price": "Расчетная цена", + "price_note": "Это приблизительная стоимость. Итоговая цена может варьироваться в зависимости от сложности проекта.", + "summary": "Сводка проекта", + "get_quote": "Получить подробное предложение", + "contact_note": "Свяжитесь с нами для подробной консультации и точного предложения.", + "recalculate": "Рассчитать снова", + "selected_services": "Выбранные услуги", + "complexity": "Сложность", + "timeline": "Сроки" + } + }, + "contact": { + "hero": { + "title": "Свяжитесь с нами", + "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" + }, + "ready_title": "Готовы начать свой проект?", + "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", + "form": { + "title": "Запрос проекта", + "name": "Имя", + "email": "Email", + "phone": "Телефон", + "message": "Сообщение", + "submit": "Отправить запрос", + "success": "Запрос успешно отправлен", + "error": "Произошла ошибка при отправке запроса", + "service": { + "title": "Интересующая услуга", + "select": "Выберите интересующую услугу", + "web": "Веб-разработка", + "mobile": "Мобильное приложение", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Консультирование", + "other": "Другое" + } + }, + "info": { + "title": "Контактная информация" + }, + "phone": { + "title": "Телефонный запрос", + "number": "+82-2-1234-5678", + "hours": "Пн-Пт 9:00-18:00" + }, + "email": { + "title": "Email запрос", + "address": "info@smartsoltech.co.kr", + "response": "Ответ в течение 24 часов" + }, + "telegram": { + "title": "Telegram", + "subtitle": "Для быстрого ответа" + }, + "address": { + "title": "Адрес офиса", + "line1": "ул. Тегеран-ро, 123, Каннам-гу", + "line2": "Сеул, Южная Корея" + }, + "cta": { + "ready": "Готовы?", + "start": "Начать", + "question": "Есть вопросы?", + "subtitle": "Мы предоставляем консультации по проектам" + }, + "meta": { + "title": "Контакты", + "description": "Свяжитесь с нами в любое время для запросов по проектам или консультации" + } + }, + "about": { + "hero": { + "title": "О SmartSolTech", + "subtitle": "Создаем будущее с инновациями и технологиями" + }, + "company": { + "title": "Информация о компании", + "description1": "SmartSolTech - технологическая компания, основанная в 2020 году, признанная за экспертизу в веб-разработке, разработке мобильных приложений и UI/UX дизайне.", + "description2": "Мы точно понимаем потребности клиентов и предоставляем инновационные решения, используя новейшие технологии." + }, + "stats": { + "projects": "Завершенных проектов", + "experience": "Лет опыта", + "clients": "Довольных клиентов" + }, + "mission": { + "title": "Наша миссия", + "description": "Наша миссия - поддерживать рост бизнеса клиентов через технологии и лидировать в цифровых инновациях." + }, + "values": { + "innovation": { + "title": "Инновации", + "description": "Мы предоставляем инновационные решения через постоянные исследования и внедрение передовых технологий." + }, + "quality": { + "title": "Качество", + "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." + }, + "partnership": { + "title": "Партнерство", + "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." + } + }, + "cta": { + "title": "Мы будем расти вместе", + "subtitle": "Превратите свои идеи в реальность", + "button": "Связаться с нами" + }, + "meta": { + "title": "О нас", + "description": "SmartSolTech - профессиональная компания разработки, которая поддерживает рост бизнеса клиентов инновационными технологиями" + } + }, + "footer": { + "description": "Специалист цифровых решений, лидирующий в инновациях", + "company": { + "description": "Специалист цифровых решений, лидирующий в инновациях" + }, + "links": { + "title": "Быстрые ссылки" + }, + "contact": { + "title": "Контакты", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "ул. Тегеран-ро, 123, Каннам-гу, Сеул" + }, + "copyright": "© {{year}} SmartSolTech. Все права защищены.", + "privacy": "Политика конфиденциальности", + "terms": "Условия использования" + }, + "theme": { + "light": "Светлая тема", + "dark": "Темная тема", + "toggle": "Переключить тему" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "Загрузка...", + "error": "Произошла ошибка", + "success": "Успешно", + "view_more": "Смотреть больше", + "back": "Назад", + "next": "Далее", + "previous": "Предыдущий", + "view_details": "Подробнее" + }, + "meta": { + "description": "SmartSolTech - Инновационная веб-разработка, разработка мобильных приложений, UI/UX дизайн", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, Корея", + "title": "SmartSolTech" + }, + "nav": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "calculator": "Калькулятор" + }, + "admin": { + "login": "Вход в админ-панель", + "dashboard": "Панель управления", + "title": "SmartSolTech Админ" + }, + "company": { + "name": "SmartSolTech", + "description": "Специалист цифровых решений, лидирующий в инновациях", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "Страница не найдена", + "error_occurred": "Произошла ошибка", + "title": "Ошибка - SmartSolTech", + "default_title": "Произошла ошибка", + "default_message": "Возникла проблема при обработке запроса.", + "back_home": "Вернуться на главную", + "go_back": "Вернуться", + "need_help": "Нужна помощь?", + "help_message": "Если проблема повторяется, свяжитесь с нами в любое время.", + "contact_support": "Связаться с поддержкой" + }, + "pages": { + "home": "Главная страница", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор" + } +} \ No newline at end of file diff --git a/.history/locales/ru_20251025215055.json b/.history/locales/ru_20251025215055.json new file mode 100644 index 0000000..17c2132 --- /dev/null +++ b/.history/locales/ru_20251025215055.json @@ -0,0 +1,424 @@ +{ + "navigation": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор", + "admin": "Админ" + }, + "hero": { + "title": { + "smart": "Умные", + "solutions": "Решения" + }, + "subtitle": "Развивайте свой бизнес с инновационными технологиями", + "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", + "cta": { + "start": "Начать", + "portfolio": "Смотреть портфолио" + } + }, + "services": { + "title": { + "our": "Наши", + "services": "Услуги" + }, + "subtitle": "Профессиональные услуги разработки для воплощения ваших идей в реальность", + "description": "Цифровые решения с использованием передовых технологий и творческих идей", + "view_all": "Посмотреть все услуги", + "web": { + "title": "Веб-разработка", + "description": "Адаптивные сайты и веб-приложения", + "price": "От $500" + }, + "mobile": { + "title": "Мобильные приложения", + "description": "Разработка нативных приложений для iOS и Android", + "price": "От $1,000" + }, + "design": { + "title": "UI/UX Дизайн", + "description": "Дизайн интерфейсов и пользовательского опыта", + "price": "От $300" + }, + "marketing": { + "title": "Цифровой маркетинг", + "description": "SEO, SMM, управление рекламой", + "price": "От $200" + }, + "meta": { + "title": "Услуги", + "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" + }, + "hero": { + "title": "Наши", + "title_highlight": "Услуги", + "subtitle": "Поддержка роста бизнеса с инновационными технологиями" + }, + "cards": { + "starting_price": "Начальная цена", + "consultation": "консультация", + "contact": "Связаться", + "calculate_cost": "Рассчитать стоимость", + "popular": "Популярно", + "coming_soon": "Услуги скоро появятся", + "coming_soon_desc": "Скоро мы предложим различные услуги!" + }, + "process": { + "title": "Процесс реализации проекта", + "subtitle": "Мы ведем проекты с системным и профессиональным подходом", + "step1": { + "title": "Консультация и планирование", + "description": "Точно понимаем требования клиента и планируем оптимальное решение" + }, + "step2": { + "title": "Дизайн и проектирование", + "description": "Проектируем интуитивный дизайн, ориентированный на пользователя, и надежную системную архитектуру" + }, + "step3": { + "title": "Разработка и реализация", + "description": "Разрабатываем эффективные и масштабируемые решения, используя новейшие технологии и лучшие практики" + }, + "step4": { + "title": "Тестирование и развертывание", + "description": "Обеспечиваем качество через тщательное тестирование и проводим стабильное развертывание" + } + }, + "why_choose": { + "title": "Почему стоит выбрать SmartSolTech?", + "modern_tech": { + "title": "Использование современных технологий", + "description": "Всегда отслеживаем новейшие технологические тренды и используем проверенный технологический стек для предоставления футуристических решений." + }, + "expert_team": { + "title": "Экспертная команда", + "description": "Команда, состоящая из экспертов в каждой области, сотрудничает для обеспечения результатов высочайшего качества." + }, + "fast_response": { + "title": "Быстрое реагирование", + "description": "Завершаем проекты в установленные сроки благодаря быстрой коммуникации и эффективному управлению проектами." + }, + "continuous_support": { + "title": "Постоянная поддержка", + "description": "Поддерживаем долгосрочное партнерство через постоянное обслуживание и техническую поддержку даже после завершения проекта." + }, + "quality_guarantee": { + "title": "Гарантия качества", + "subtitle": "Для удовлетворения клиентов\nСервис высочайшего качества" + } + }, + "cta": { + "title": "Готовы начать проект?", + "subtitle": "Мы предложим оптимальное решение через бесплатную консультацию", + "free_consultation": "Подать заявку на бесплатную консультацию", + "calculate_cost": "Рассчитать стоимость", + "view_portfolio": "Посмотреть портфолио" + } + }, + "portfolio": { + "title": { + "recent": "Недавние", + "projects": "Проекты" + }, + "subtitle": "Ознакомьтесь с успешно завершенными проектами", + "description": "Посмотрите на проекты, выполненные для успеха клиентов", + "view_details": "Подробнее", + "view_all": "Посмотреть все портфолио", + "categories": { + "all": "Все", + "web": "Веб-разработка", + "mobile": "Мобильные приложения", + "uiux": "UI/UX Дизайн" + }, + "project_details": "Детали проекта", + "default": { + "ecommerce": "Электронная коммерция", + "title": "Платформа электронной коммерции", + "description": "Современное решение для интернет-торговли с интуитивным интерфейсом" + }, + "meta": { + "title": "Портфолио", + "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", + "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Наше портфолио", + "subtitle": "Откройте для себя инновационные проекты и креативные решения", + "categories": { + "all": "Все", + "web-development": "Веб-разработка", + "mobile-app": "Мобильные приложения", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифровой маркетинг" + }, + "buttons": { + "details": "Подробнее", + "projectDetails": "Детали проекта", + "loadMore": "Загрузить больше проектов", + "contact": "Заказать проект", + "calculate": "Рассчитать стоимость" + }, + "empty": { + "title": "Портфолио пока пусто", + "subtitle": "Скоро мы представим потрясающие проекты!" + }, + "cta": { + "title": "Станьте звездой следующего проекта", + "subtitle": "Создавайте инновационные цифровые решения вместе с нами" + }, + "labels": { + "featured": "РЕКОМЕНДУЕМОЕ", + "views": "просмотров", + "likes": "лайков" + } + }, + "calculator": { + "title": "Калькулятор стоимости проекта", + "subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в реальном времени", + "next_step": "Следующий шаг", + "prev_step": "Предыдущий шаг", + "calculate": "Рассчитать", + "reset": "Сброс", + "meta": { + "title": "Калькулятор стоимости проекта", + "description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора" + }, + "cta": { + "title": "Проверьте оценку вашего проекта", + "subtitle": "Выберите нужные услуги и требования для расчета стоимости в реальном времени", + "button": "Использовать калькулятор стоимости" + }, + "step1": { + "title": "Выберите ваши услуги", + "nav_title": "Шаг 1: Услуги", + "subtitle": "Выберите услуги, необходимые для вашего проекта" + }, + "step2": { + "title": "Детали проекта", + "nav_title": "Шаг 2: Детали", + "subtitle": "Расскажите нам о сложности и сроках вашего проекта" + }, + "complexity": { + "title": "Сложность проекта", + "simple": "Простой", + "simple_desc": "Базовые функции и стандартный функционал", + "medium": "Средний", + "medium_desc": "Пользовательские функции и интеграции", + "complex": "Сложный", + "complex_desc": "Продвинутые функции и сложные интеграции" + }, + "timeline": { + "title": "Сроки проекта", + "standard": "Стандартные (4-8 недель)", + "standard_desc": "Обычные сроки разработки", + "rush": "Срочные (2-4 недели)", + "rush_desc": "Ускоренная доставка с дополнительной стоимостью", + "extended": "Расширенные (8+ недель)", + "extended_desc": "Гибкие сроки с оптимизацией стоимости" + }, + "result": { + "title": "Оценка проекта", + "subtitle": "Ваша расчетная стоимость проекта", + "nav_title": "Результат", + "estimated_price": "Расчетная цена", + "price_note": "Это приблизительная стоимость. Итоговая цена может варьироваться в зависимости от сложности проекта.", + "summary": "Сводка проекта", + "get_quote": "Получить подробное предложение", + "contact_note": "Свяжитесь с нами для подробной консультации и точного предложения.", + "recalculate": "Рассчитать снова", + "selected_services": "Выбранные услуги", + "complexity": "Сложность", + "timeline": "Сроки" + } + }, + "contact": { + "hero": { + "title": "Свяжитесь с нами", + "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" + }, + "ready_title": "Готовы начать свой проект?", + "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", + "form": { + "title": "Запрос проекта", + "name": "Имя", + "email": "Email", + "phone": "Телефон", + "message": "Сообщение", + "submit": "Отправить запрос", + "success": "Запрос успешно отправлен", + "error": "Произошла ошибка при отправке запроса", + "service": { + "title": "Интересующая услуга", + "select": "Выберите интересующую услугу", + "web": "Веб-разработка", + "mobile": "Мобильное приложение", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Консультирование", + "other": "Другое" + } + }, + "info": { + "title": "Контактная информация" + }, + "phone": { + "title": "Телефонный запрос", + "number": "+82-2-1234-5678", + "hours": "Пн-Пт 9:00-18:00" + }, + "email": { + "title": "Email запрос", + "address": "info@smartsoltech.co.kr", + "response": "Ответ в течение 24 часов" + }, + "telegram": { + "title": "Telegram", + "subtitle": "Для быстрого ответа" + }, + "address": { + "title": "Адрес офиса", + "line1": "ул. Тегеран-ро, 123, Каннам-гу", + "line2": "Сеул, Южная Корея" + }, + "cta": { + "ready": "Готовы?", + "start": "Начать", + "question": "Есть вопросы?", + "subtitle": "Мы предоставляем консультации по проектам" + }, + "meta": { + "title": "Контакты", + "description": "Свяжитесь с нами в любое время для запросов по проектам или консультации" + } + }, + "about": { + "hero": { + "title": "О SmartSolTech", + "subtitle": "Создаем будущее с инновациями и технологиями" + }, + "company": { + "title": "Информация о компании", + "description1": "SmartSolTech - технологическая компания, основанная в 2020 году, признанная за экспертизу в веб-разработке, разработке мобильных приложений и UI/UX дизайне.", + "description2": "Мы точно понимаем потребности клиентов и предоставляем инновационные решения, используя новейшие технологии." + }, + "stats": { + "projects": "Завершенных проектов", + "experience": "Лет опыта", + "clients": "Довольных клиентов" + }, + "mission": { + "title": "Наша миссия", + "description": "Наша миссия - поддерживать рост бизнеса клиентов через технологии и лидировать в цифровых инновациях." + }, + "values": { + "innovation": { + "title": "Инновации", + "description": "Мы предоставляем инновационные решения через постоянные исследования и внедрение передовых технологий." + }, + "quality": { + "title": "Качество", + "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." + }, + "partnership": { + "title": "Партнерство", + "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." + } + }, + "cta": { + "title": "Мы будем расти вместе", + "subtitle": "Превратите свои идеи в реальность", + "button": "Связаться с нами" + }, + "meta": { + "title": "О нас", + "description": "SmartSolTech - профессиональная компания разработки, которая поддерживает рост бизнеса клиентов инновационными технологиями" + } + }, + "footer": { + "description": "Специалист цифровых решений, лидирующий в инновациях", + "company": { + "description": "Специалист цифровых решений, лидирующий в инновациях" + }, + "links": { + "title": "Быстрые ссылки" + }, + "contact": { + "title": "Контакты", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "ул. Тегеран-ро, 123, Каннам-гу, Сеул" + }, + "copyright": "© {{year}} SmartSolTech. Все права защищены.", + "privacy": "Политика конфиденциальности", + "terms": "Условия использования" + }, + "theme": { + "light": "Светлая тема", + "dark": "Темная тема", + "toggle": "Переключить тему" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "Загрузка...", + "error": "Произошла ошибка", + "success": "Успешно", + "view_more": "Смотреть больше", + "back": "Назад", + "next": "Далее", + "previous": "Предыдущий", + "view_details": "Подробнее" + }, + "meta": { + "description": "SmartSolTech - Инновационная веб-разработка, разработка мобильных приложений, UI/UX дизайн", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, Корея", + "title": "SmartSolTech" + }, + "nav": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "calculator": "Калькулятор" + }, + "admin": { + "login": "Вход в админ-панель", + "dashboard": "Панель управления", + "title": "SmartSolTech Админ" + }, + "company": { + "name": "SmartSolTech", + "description": "Специалист цифровых решений, лидирующий в инновациях", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "Страница не найдена", + "error_occurred": "Произошла ошибка", + "title": "Ошибка - SmartSolTech", + "default_title": "Произошла ошибка", + "default_message": "Возникла проблема при обработке запроса.", + "back_home": "Вернуться на главную", + "go_back": "Вернуться", + "need_help": "Нужна помощь?", + "help_message": "Если проблема повторяется, свяжитесь с нами в любое время.", + "contact_support": "Связаться с поддержкой" + }, + "pages": { + "home": "Главная страница", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор" + } +} \ No newline at end of file diff --git a/.history/locales/ru_20251026095239.json b/.history/locales/ru_20251026095239.json new file mode 100644 index 0000000..8b62965 --- /dev/null +++ b/.history/locales/ru_20251026095239.json @@ -0,0 +1,425 @@ +{ + "navigation": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор", + "admin": "Админ" + }, + "hero": { + "title": { + "smart": "Умные", + "solutions": "Решения" + }, + "subtitle": "Развивайте свой бизнес с инновационными технологиями", + "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", + "cta": { + "start": "Начать", + "portfolio": "Смотреть портфолио" + } + }, + "services": { + "title": { + "our": "Наши", + "services": "Услуги" + }, + "subtitle": "Профессиональные услуги разработки для воплощения ваших идей в реальность", + "description": "Цифровые решения с использованием передовых технологий и творческих идей", + "view_all": "Посмотреть все услуги", + "web": { + "title": "Веб-разработка", + "description": "Адаптивные сайты и веб-приложения", + "price": "От $500" + }, + "mobile": { + "title": "Мобильные приложения", + "description": "Разработка нативных приложений для iOS и Android", + "price": "От $1,000" + }, + "design": { + "title": "UI/UX Дизайн", + "description": "Дизайн интерфейсов и пользовательского опыта", + "price": "От $300" + }, + "marketing": { + "title": "Цифровой маркетинг", + "description": "SEO, SMM, управление рекламой", + "price": "От $200" + }, + "meta": { + "title": "Услуги", + "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" + }, + "hero": { + "title": "Наши", + "title_highlight": "Услуги", + "subtitle": "Поддержка роста бизнеса с инновационными технологиями" + }, + "cards": { + "starting_price": "Начальная цена", + "consultation": "консультация", + "contact": "Связаться", + "calculate_cost": "Рассчитать стоимость", + "popular": "Популярно", + "coming_soon": "Услуги скоро появятся", + "coming_soon_desc": "Скоро мы предложим различные услуги!" + }, + "process": { + "title": "Процесс реализации проекта", + "subtitle": "Мы ведем проекты с системным и профессиональным подходом", + "step1": { + "title": "Консультация и планирование", + "description": "Точно понимаем требования клиента и планируем оптимальное решение" + }, + "step2": { + "title": "Дизайн и проектирование", + "description": "Проектируем интуитивный дизайн, ориентированный на пользователя, и надежную системную архитектуру" + }, + "step3": { + "title": "Разработка и реализация", + "description": "Разрабатываем эффективные и масштабируемые решения, используя новейшие технологии и лучшие практики" + }, + "step4": { + "title": "Тестирование и развертывание", + "description": "Обеспечиваем качество через тщательное тестирование и проводим стабильное развертывание" + } + }, + "why_choose": { + "title": "Почему стоит выбрать SmartSolTech?", + "modern_tech": { + "title": "Использование современных технологий", + "description": "Всегда отслеживаем новейшие технологические тренды и используем проверенный технологический стек для предоставления футуристических решений." + }, + "expert_team": { + "title": "Экспертная команда", + "description": "Команда, состоящая из экспертов в каждой области, сотрудничает для обеспечения результатов высочайшего качества." + }, + "fast_response": { + "title": "Быстрое реагирование", + "description": "Завершаем проекты в установленные сроки благодаря быстрой коммуникации и эффективному управлению проектами." + }, + "continuous_support": { + "title": "Постоянная поддержка", + "description": "Поддерживаем долгосрочное партнерство через постоянное обслуживание и техническую поддержку даже после завершения проекта." + }, + "quality_guarantee": { + "title": "Гарантия качества", + "subtitle": "Для удовлетворения клиентов\nСервис высочайшего качества" + } + }, + "cta": { + "title": "Готовы начать проект?", + "subtitle": "Мы предложим оптимальное решение через бесплатную консультацию", + "free_consultation": "Подать заявку на бесплатную консультацию", + "calculate_cost": "Рассчитать стоимость", + "view_portfolio": "Посмотреть портфолио" + } + }, + "portfolio": { + "title": { + "recent": "Недавние", + "projects": "Проекты" + }, + "subtitle": "Ознакомьтесь с успешно завершенными проектами", + "description": "Посмотрите на проекты, выполненные для успеха клиентов", + "view_details": "Подробнее", + "view_all": "Посмотреть все портфолио", + "categories": { + "all": "Все", + "web": "Веб-разработка", + "mobile": "Мобильные приложения", + "uiux": "UI/UX Дизайн" + }, + "project_details": "Детали проекта", + "default": { + "ecommerce": "Электронная коммерция", + "title": "Платформа электронной коммерции", + "description": "Современное решение для интернет-торговли с интуитивным интерфейсом" + }, + "meta": { + "title": "Портфолио", + "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", + "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Наше портфолио", + "subtitle": "Откройте для себя инновационные проекты и креативные решения", + "categories": { + "all": "Все", + "web-development": "Веб-разработка", + "mobile-app": "Мобильные приложения", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифровой маркетинг" + }, + "buttons": { + "details": "Подробнее", + "projectDetails": "Детали проекта", + "loadMore": "Загрузить больше проектов", + "contact": "Заказать проект", + "calculate": "Рассчитать стоимость" + }, + "empty": { + "title": "Портфолио пока пусто", + "subtitle": "Скоро мы представим потрясающие проекты!" + }, + "cta": { + "title": "Станьте звездой следующего проекта", + "subtitle": "Создавайте инновационные цифровые решения вместе с нами" + }, + "labels": { + "featured": "РЕКОМЕНДУЕМОЕ", + "views": "просмотров", + "likes": "лайков" + } + }, + "calculator": { + "title": "Калькулятор стоимости проекта", + "subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в реальном времени", + "next_step": "Следующий шаг", + "prev_step": "Предыдущий шаг", + "calculate": "Рассчитать", + "reset": "Сброс", + "live_update": "Обновление в реальном времени", + "meta": { + "title": "Калькулятор стоимости проекта", + "description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора" + }, + "cta": { + "title": "Проверьте оценку вашего проекта", + "subtitle": "Выберите нужные услуги и требования для расчета стоимости в реальном времени", + "button": "Использовать калькулятор стоимости" + }, + "step1": { + "title": "Выберите ваши услуги", + "nav_title": "Шаг 1: Услуги", + "subtitle": "Выберите услуги, необходимые для вашего проекта" + }, + "step2": { + "title": "Детали проекта", + "nav_title": "Шаг 2: Детали", + "subtitle": "Расскажите нам о сложности и сроках вашего проекта" + }, + "complexity": { + "title": "Сложность проекта", + "simple": "Простой", + "simple_desc": "Базовые функции и стандартный функционал", + "medium": "Средний", + "medium_desc": "Пользовательские функции и интеграции", + "complex": "Сложный", + "complex_desc": "Продвинутые функции и сложные интеграции" + }, + "timeline": { + "title": "Сроки проекта", + "standard": "Стандартные (4-8 недель)", + "standard_desc": "Обычные сроки разработки", + "rush": "Срочные (2-4 недели)", + "rush_desc": "Ускоренная доставка с дополнительной стоимостью", + "extended": "Расширенные (8+ недель)", + "extended_desc": "Гибкие сроки с оптимизацией стоимости" + }, + "result": { + "title": "Оценка проекта", + "subtitle": "Ваша расчетная стоимость проекта", + "nav_title": "Результат", + "estimated_price": "Расчетная цена", + "price_note": "Это приблизительная стоимость. Итоговая цена может варьироваться в зависимости от сложности проекта.", + "summary": "Сводка проекта", + "get_quote": "Получить подробное предложение", + "contact_note": "Свяжитесь с нами для подробной консультации и точного предложения.", + "recalculate": "Рассчитать снова", + "selected_services": "Выбранные услуги", + "complexity": "Сложность", + "timeline": "Сроки" + } + }, + "contact": { + "hero": { + "title": "Свяжитесь с нами", + "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" + }, + "ready_title": "Готовы начать свой проект?", + "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", + "form": { + "title": "Запрос проекта", + "name": "Имя", + "email": "Email", + "phone": "Телефон", + "message": "Сообщение", + "submit": "Отправить запрос", + "success": "Запрос успешно отправлен", + "error": "Произошла ошибка при отправке запроса", + "service": { + "title": "Интересующая услуга", + "select": "Выберите интересующую услугу", + "web": "Веб-разработка", + "mobile": "Мобильное приложение", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Консультирование", + "other": "Другое" + } + }, + "info": { + "title": "Контактная информация" + }, + "phone": { + "title": "Телефонный запрос", + "number": "+82-2-1234-5678", + "hours": "Пн-Пт 9:00-18:00" + }, + "email": { + "title": "Email запрос", + "address": "info@smartsoltech.co.kr", + "response": "Ответ в течение 24 часов" + }, + "telegram": { + "title": "Telegram", + "subtitle": "Для быстрого ответа" + }, + "address": { + "title": "Адрес офиса", + "line1": "ул. Тегеран-ро, 123, Каннам-гу", + "line2": "Сеул, Южная Корея" + }, + "cta": { + "ready": "Готовы?", + "start": "Начать", + "question": "Есть вопросы?", + "subtitle": "Мы предоставляем консультации по проектам" + }, + "meta": { + "title": "Контакты", + "description": "Свяжитесь с нами в любое время для запросов по проектам или консультации" + } + }, + "about": { + "hero": { + "title": "О SmartSolTech", + "subtitle": "Создаем будущее с инновациями и технологиями" + }, + "company": { + "title": "Информация о компании", + "description1": "SmartSolTech - технологическая компания, основанная в 2020 году, признанная за экспертизу в веб-разработке, разработке мобильных приложений и UI/UX дизайне.", + "description2": "Мы точно понимаем потребности клиентов и предоставляем инновационные решения, используя новейшие технологии." + }, + "stats": { + "projects": "Завершенных проектов", + "experience": "Лет опыта", + "clients": "Довольных клиентов" + }, + "mission": { + "title": "Наша миссия", + "description": "Наша миссия - поддерживать рост бизнеса клиентов через технологии и лидировать в цифровых инновациях." + }, + "values": { + "innovation": { + "title": "Инновации", + "description": "Мы предоставляем инновационные решения через постоянные исследования и внедрение передовых технологий." + }, + "quality": { + "title": "Качество", + "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." + }, + "partnership": { + "title": "Партнерство", + "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." + } + }, + "cta": { + "title": "Мы будем расти вместе", + "subtitle": "Превратите свои идеи в реальность", + "button": "Связаться с нами" + }, + "meta": { + "title": "О нас", + "description": "SmartSolTech - профессиональная компания разработки, которая поддерживает рост бизнеса клиентов инновационными технологиями" + } + }, + "footer": { + "description": "Специалист цифровых решений, лидирующий в инновациях", + "company": { + "description": "Специалист цифровых решений, лидирующий в инновациях" + }, + "links": { + "title": "Быстрые ссылки" + }, + "contact": { + "title": "Контакты", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "ул. Тегеран-ро, 123, Каннам-гу, Сеул" + }, + "copyright": "© {{year}} SmartSolTech. Все права защищены.", + "privacy": "Политика конфиденциальности", + "terms": "Условия использования" + }, + "theme": { + "light": "Светлая тема", + "dark": "Темная тема", + "toggle": "Переключить тему" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "Загрузка...", + "error": "Произошла ошибка", + "success": "Успешно", + "view_more": "Смотреть больше", + "back": "Назад", + "next": "Далее", + "previous": "Предыдущий", + "view_details": "Подробнее" + }, + "meta": { + "description": "SmartSolTech - Инновационная веб-разработка, разработка мобильных приложений, UI/UX дизайн", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, Корея", + "title": "SmartSolTech" + }, + "nav": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "calculator": "Калькулятор" + }, + "admin": { + "login": "Вход в админ-панель", + "dashboard": "Панель управления", + "title": "SmartSolTech Админ" + }, + "company": { + "name": "SmartSolTech", + "description": "Специалист цифровых решений, лидирующий в инновациях", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "Страница не найдена", + "error_occurred": "Произошла ошибка", + "title": "Ошибка - SmartSolTech", + "default_title": "Произошла ошибка", + "default_message": "Возникла проблема при обработке запроса.", + "back_home": "Вернуться на главную", + "go_back": "Вернуться", + "need_help": "Нужна помощь?", + "help_message": "Если проблема повторяется, свяжитесь с нами в любое время.", + "contact_support": "Связаться с поддержкой" + }, + "pages": { + "home": "Главная страница", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор" + } +} \ No newline at end of file diff --git a/.history/locales/ru_20251026095240.json b/.history/locales/ru_20251026095240.json new file mode 100644 index 0000000..8b62965 --- /dev/null +++ b/.history/locales/ru_20251026095240.json @@ -0,0 +1,425 @@ +{ + "navigation": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор", + "admin": "Админ" + }, + "hero": { + "title": { + "smart": "Умные", + "solutions": "Решения" + }, + "subtitle": "Развивайте свой бизнес с инновационными технологиями", + "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", + "cta": { + "start": "Начать", + "portfolio": "Смотреть портфолио" + } + }, + "services": { + "title": { + "our": "Наши", + "services": "Услуги" + }, + "subtitle": "Профессиональные услуги разработки для воплощения ваших идей в реальность", + "description": "Цифровые решения с использованием передовых технологий и творческих идей", + "view_all": "Посмотреть все услуги", + "web": { + "title": "Веб-разработка", + "description": "Адаптивные сайты и веб-приложения", + "price": "От $500" + }, + "mobile": { + "title": "Мобильные приложения", + "description": "Разработка нативных приложений для iOS и Android", + "price": "От $1,000" + }, + "design": { + "title": "UI/UX Дизайн", + "description": "Дизайн интерфейсов и пользовательского опыта", + "price": "От $300" + }, + "marketing": { + "title": "Цифровой маркетинг", + "description": "SEO, SMM, управление рекламой", + "price": "От $200" + }, + "meta": { + "title": "Услуги", + "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" + }, + "hero": { + "title": "Наши", + "title_highlight": "Услуги", + "subtitle": "Поддержка роста бизнеса с инновационными технологиями" + }, + "cards": { + "starting_price": "Начальная цена", + "consultation": "консультация", + "contact": "Связаться", + "calculate_cost": "Рассчитать стоимость", + "popular": "Популярно", + "coming_soon": "Услуги скоро появятся", + "coming_soon_desc": "Скоро мы предложим различные услуги!" + }, + "process": { + "title": "Процесс реализации проекта", + "subtitle": "Мы ведем проекты с системным и профессиональным подходом", + "step1": { + "title": "Консультация и планирование", + "description": "Точно понимаем требования клиента и планируем оптимальное решение" + }, + "step2": { + "title": "Дизайн и проектирование", + "description": "Проектируем интуитивный дизайн, ориентированный на пользователя, и надежную системную архитектуру" + }, + "step3": { + "title": "Разработка и реализация", + "description": "Разрабатываем эффективные и масштабируемые решения, используя новейшие технологии и лучшие практики" + }, + "step4": { + "title": "Тестирование и развертывание", + "description": "Обеспечиваем качество через тщательное тестирование и проводим стабильное развертывание" + } + }, + "why_choose": { + "title": "Почему стоит выбрать SmartSolTech?", + "modern_tech": { + "title": "Использование современных технологий", + "description": "Всегда отслеживаем новейшие технологические тренды и используем проверенный технологический стек для предоставления футуристических решений." + }, + "expert_team": { + "title": "Экспертная команда", + "description": "Команда, состоящая из экспертов в каждой области, сотрудничает для обеспечения результатов высочайшего качества." + }, + "fast_response": { + "title": "Быстрое реагирование", + "description": "Завершаем проекты в установленные сроки благодаря быстрой коммуникации и эффективному управлению проектами." + }, + "continuous_support": { + "title": "Постоянная поддержка", + "description": "Поддерживаем долгосрочное партнерство через постоянное обслуживание и техническую поддержку даже после завершения проекта." + }, + "quality_guarantee": { + "title": "Гарантия качества", + "subtitle": "Для удовлетворения клиентов\nСервис высочайшего качества" + } + }, + "cta": { + "title": "Готовы начать проект?", + "subtitle": "Мы предложим оптимальное решение через бесплатную консультацию", + "free_consultation": "Подать заявку на бесплатную консультацию", + "calculate_cost": "Рассчитать стоимость", + "view_portfolio": "Посмотреть портфолио" + } + }, + "portfolio": { + "title": { + "recent": "Недавние", + "projects": "Проекты" + }, + "subtitle": "Ознакомьтесь с успешно завершенными проектами", + "description": "Посмотрите на проекты, выполненные для успеха клиентов", + "view_details": "Подробнее", + "view_all": "Посмотреть все портфолио", + "categories": { + "all": "Все", + "web": "Веб-разработка", + "mobile": "Мобильные приложения", + "uiux": "UI/UX Дизайн" + }, + "project_details": "Детали проекта", + "default": { + "ecommerce": "Электронная коммерция", + "title": "Платформа электронной коммерции", + "description": "Современное решение для интернет-торговли с интуитивным интерфейсом" + }, + "meta": { + "title": "Портфолио", + "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", + "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Наше портфолио", + "subtitle": "Откройте для себя инновационные проекты и креативные решения", + "categories": { + "all": "Все", + "web-development": "Веб-разработка", + "mobile-app": "Мобильные приложения", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифровой маркетинг" + }, + "buttons": { + "details": "Подробнее", + "projectDetails": "Детали проекта", + "loadMore": "Загрузить больше проектов", + "contact": "Заказать проект", + "calculate": "Рассчитать стоимость" + }, + "empty": { + "title": "Портфолио пока пусто", + "subtitle": "Скоро мы представим потрясающие проекты!" + }, + "cta": { + "title": "Станьте звездой следующего проекта", + "subtitle": "Создавайте инновационные цифровые решения вместе с нами" + }, + "labels": { + "featured": "РЕКОМЕНДУЕМОЕ", + "views": "просмотров", + "likes": "лайков" + } + }, + "calculator": { + "title": "Калькулятор стоимости проекта", + "subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в реальном времени", + "next_step": "Следующий шаг", + "prev_step": "Предыдущий шаг", + "calculate": "Рассчитать", + "reset": "Сброс", + "live_update": "Обновление в реальном времени", + "meta": { + "title": "Калькулятор стоимости проекта", + "description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора" + }, + "cta": { + "title": "Проверьте оценку вашего проекта", + "subtitle": "Выберите нужные услуги и требования для расчета стоимости в реальном времени", + "button": "Использовать калькулятор стоимости" + }, + "step1": { + "title": "Выберите ваши услуги", + "nav_title": "Шаг 1: Услуги", + "subtitle": "Выберите услуги, необходимые для вашего проекта" + }, + "step2": { + "title": "Детали проекта", + "nav_title": "Шаг 2: Детали", + "subtitle": "Расскажите нам о сложности и сроках вашего проекта" + }, + "complexity": { + "title": "Сложность проекта", + "simple": "Простой", + "simple_desc": "Базовые функции и стандартный функционал", + "medium": "Средний", + "medium_desc": "Пользовательские функции и интеграции", + "complex": "Сложный", + "complex_desc": "Продвинутые функции и сложные интеграции" + }, + "timeline": { + "title": "Сроки проекта", + "standard": "Стандартные (4-8 недель)", + "standard_desc": "Обычные сроки разработки", + "rush": "Срочные (2-4 недели)", + "rush_desc": "Ускоренная доставка с дополнительной стоимостью", + "extended": "Расширенные (8+ недель)", + "extended_desc": "Гибкие сроки с оптимизацией стоимости" + }, + "result": { + "title": "Оценка проекта", + "subtitle": "Ваша расчетная стоимость проекта", + "nav_title": "Результат", + "estimated_price": "Расчетная цена", + "price_note": "Это приблизительная стоимость. Итоговая цена может варьироваться в зависимости от сложности проекта.", + "summary": "Сводка проекта", + "get_quote": "Получить подробное предложение", + "contact_note": "Свяжитесь с нами для подробной консультации и точного предложения.", + "recalculate": "Рассчитать снова", + "selected_services": "Выбранные услуги", + "complexity": "Сложность", + "timeline": "Сроки" + } + }, + "contact": { + "hero": { + "title": "Свяжитесь с нами", + "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" + }, + "ready_title": "Готовы начать свой проект?", + "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", + "form": { + "title": "Запрос проекта", + "name": "Имя", + "email": "Email", + "phone": "Телефон", + "message": "Сообщение", + "submit": "Отправить запрос", + "success": "Запрос успешно отправлен", + "error": "Произошла ошибка при отправке запроса", + "service": { + "title": "Интересующая услуга", + "select": "Выберите интересующую услугу", + "web": "Веб-разработка", + "mobile": "Мобильное приложение", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Консультирование", + "other": "Другое" + } + }, + "info": { + "title": "Контактная информация" + }, + "phone": { + "title": "Телефонный запрос", + "number": "+82-2-1234-5678", + "hours": "Пн-Пт 9:00-18:00" + }, + "email": { + "title": "Email запрос", + "address": "info@smartsoltech.co.kr", + "response": "Ответ в течение 24 часов" + }, + "telegram": { + "title": "Telegram", + "subtitle": "Для быстрого ответа" + }, + "address": { + "title": "Адрес офиса", + "line1": "ул. Тегеран-ро, 123, Каннам-гу", + "line2": "Сеул, Южная Корея" + }, + "cta": { + "ready": "Готовы?", + "start": "Начать", + "question": "Есть вопросы?", + "subtitle": "Мы предоставляем консультации по проектам" + }, + "meta": { + "title": "Контакты", + "description": "Свяжитесь с нами в любое время для запросов по проектам или консультации" + } + }, + "about": { + "hero": { + "title": "О SmartSolTech", + "subtitle": "Создаем будущее с инновациями и технологиями" + }, + "company": { + "title": "Информация о компании", + "description1": "SmartSolTech - технологическая компания, основанная в 2020 году, признанная за экспертизу в веб-разработке, разработке мобильных приложений и UI/UX дизайне.", + "description2": "Мы точно понимаем потребности клиентов и предоставляем инновационные решения, используя новейшие технологии." + }, + "stats": { + "projects": "Завершенных проектов", + "experience": "Лет опыта", + "clients": "Довольных клиентов" + }, + "mission": { + "title": "Наша миссия", + "description": "Наша миссия - поддерживать рост бизнеса клиентов через технологии и лидировать в цифровых инновациях." + }, + "values": { + "innovation": { + "title": "Инновации", + "description": "Мы предоставляем инновационные решения через постоянные исследования и внедрение передовых технологий." + }, + "quality": { + "title": "Качество", + "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." + }, + "partnership": { + "title": "Партнерство", + "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." + } + }, + "cta": { + "title": "Мы будем расти вместе", + "subtitle": "Превратите свои идеи в реальность", + "button": "Связаться с нами" + }, + "meta": { + "title": "О нас", + "description": "SmartSolTech - профессиональная компания разработки, которая поддерживает рост бизнеса клиентов инновационными технологиями" + } + }, + "footer": { + "description": "Специалист цифровых решений, лидирующий в инновациях", + "company": { + "description": "Специалист цифровых решений, лидирующий в инновациях" + }, + "links": { + "title": "Быстрые ссылки" + }, + "contact": { + "title": "Контакты", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "ул. Тегеран-ро, 123, Каннам-гу, Сеул" + }, + "copyright": "© {{year}} SmartSolTech. Все права защищены.", + "privacy": "Политика конфиденциальности", + "terms": "Условия использования" + }, + "theme": { + "light": "Светлая тема", + "dark": "Темная тема", + "toggle": "Переключить тему" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "Загрузка...", + "error": "Произошла ошибка", + "success": "Успешно", + "view_more": "Смотреть больше", + "back": "Назад", + "next": "Далее", + "previous": "Предыдущий", + "view_details": "Подробнее" + }, + "meta": { + "description": "SmartSolTech - Инновационная веб-разработка, разработка мобильных приложений, UI/UX дизайн", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, Корея", + "title": "SmartSolTech" + }, + "nav": { + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "calculator": "Калькулятор" + }, + "admin": { + "login": "Вход в админ-панель", + "dashboard": "Панель управления", + "title": "SmartSolTech Админ" + }, + "company": { + "name": "SmartSolTech", + "description": "Специалист цифровых решений, лидирующий в инновациях", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "Страница не найдена", + "error_occurred": "Произошла ошибка", + "title": "Ошибка - SmartSolTech", + "default_title": "Произошла ошибка", + "default_message": "Возникла проблема при обработке запроса.", + "back_home": "Вернуться на главную", + "go_back": "Вернуться", + "need_help": "Нужна помощь?", + "help_message": "Если проблема повторяется, свяжитесь с нами в любое время.", + "contact_support": "Связаться с поддержкой" + }, + "pages": { + "home": "Главная страница", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор" + } +} \ No newline at end of file diff --git a/.history/middleware/auth_20251019163222.js b/.history/middleware/auth_20251019163222.js deleted file mode 100644 index e1aa95c..0000000 --- a/.history/middleware/auth_20251019163222.js +++ /dev/null @@ -1,154 +0,0 @@ -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 -}; \ No newline at end of file diff --git a/.history/middleware/auth_20251019163807.js b/.history/middleware/auth_20251019163807.js deleted file mode 100644 index e1aa95c..0000000 --- a/.history/middleware/auth_20251019163807.js +++ /dev/null @@ -1,154 +0,0 @@ -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 -}; \ No newline at end of file diff --git a/.history/middleware/auth_20251019202041.js b/.history/middleware/auth_20251019202041.js deleted file mode 100644 index 6a338bd..0000000 --- a/.history/middleware/auth_20251019202041.js +++ /dev/null @@ -1,154 +0,0 @@ -const jwt = require('jsonwebtoken'); -const { User } = require('../models'); - -/** - * 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 -}; \ No newline at end of file diff --git a/.history/middleware/auth_20251019202048.js b/.history/middleware/auth_20251019202048.js deleted file mode 100644 index a76c5f2..0000000 --- a/.history/middleware/auth_20251019202048.js +++ /dev/null @@ -1,156 +0,0 @@ -const jwt = require('jsonwebtoken'); -const { User } = require('../models'); - -/** - * 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.findByPk(decoded.userId, { - attributes: { exclude: ['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 -}; \ No newline at end of file diff --git a/.history/middleware/auth_20251019202102.js b/.history/middleware/auth_20251019202102.js deleted file mode 100644 index 50bd0e7..0000000 --- a/.history/middleware/auth_20251019202102.js +++ /dev/null @@ -1,158 +0,0 @@ -const jwt = require('jsonwebtoken'); -const { User } = require('../models'); - -/** - * 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.findByPk(decoded.userId, { - attributes: { exclude: ['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.findByPk(req.session.userId, { - attributes: { exclude: ['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 -}; \ No newline at end of file diff --git a/.history/middleware/auth_20251019202112.js b/.history/middleware/auth_20251019202112.js deleted file mode 100644 index cee7633..0000000 --- a/.history/middleware/auth_20251019202112.js +++ /dev/null @@ -1,162 +0,0 @@ -const jwt = require('jsonwebtoken'); -const { User } = require('../models'); - -/** - * 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.findByPk(decoded.userId, { - attributes: { exclude: ['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.findByPk(req.session.userId, { - attributes: { exclude: ['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.findByPk(req.session.userId, { - attributes: { exclude: ['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.findByPk(decoded.userId, { - attributes: { exclude: ['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 -}; \ No newline at end of file diff --git a/.history/middleware/auth_20251019202631.js b/.history/middleware/auth_20251019202631.js deleted file mode 100644 index cee7633..0000000 --- a/.history/middleware/auth_20251019202631.js +++ /dev/null @@ -1,162 +0,0 @@ -const jwt = require('jsonwebtoken'); -const { User } = require('../models'); - -/** - * 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.findByPk(decoded.userId, { - attributes: { exclude: ['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.findByPk(req.session.userId, { - attributes: { exclude: ['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.findByPk(req.session.userId, { - attributes: { exclude: ['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.findByPk(decoded.userId, { - attributes: { exclude: ['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 -}; \ No newline at end of file diff --git a/.history/middleware/validation_20251019163300.js b/.history/middleware/validation_20251019163300.js deleted file mode 100644 index b19ca74..0000000 --- a/.history/middleware/validation_20251019163300.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * 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 -}; \ No newline at end of file diff --git a/.history/middleware/validation_20251019163807.js b/.history/middleware/validation_20251019163807.js deleted file mode 100644 index b19ca74..0000000 --- a/.history/middleware/validation_20251019163807.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * 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 -}; \ No newline at end of file diff --git a/.history/models/Banner_20251022194806.js b/.history/models/Banner_20251022194806.js deleted file mode 100644 index ab3dd28..0000000 --- a/.history/models/Banner_20251022194806.js +++ /dev/null @@ -1,195 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Banner = sequelize.define('Banner', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - title: { - type: DataTypes.STRING, - allowNull: false, - validate: { - notEmpty: true, - len: [1, 200] - } - }, - subtitle: { - type: DataTypes.STRING, - allowNull: true, - validate: { - len: [0, 300] - } - }, - description: { - type: DataTypes.TEXT, - allowNull: true - }, - buttonText: { - type: DataTypes.STRING, - allowNull: true, - validate: { - len: [0, 50] - } - }, - buttonUrl: { - type: DataTypes.STRING, - allowNull: true, - validate: { - isUrl: true - } - }, - image: { - type: DataTypes.STRING, - allowNull: true, - comment: 'Banner background image URL' - }, - mobileImage: { - type: DataTypes.STRING, - allowNull: true, - comment: 'Mobile-optimized banner image URL' - }, - position: { - type: DataTypes.ENUM('hero', 'secondary', 'footer'), - defaultValue: 'hero', - allowNull: false - }, - order: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - comment: 'Display order (lower numbers appear first)' - }, - isActive: { - type: DataTypes.BOOLEAN, - defaultValue: true, - allowNull: false - }, - startDate: { - type: DataTypes.DATE, - allowNull: true, - comment: 'Banner start display date' - }, - endDate: { - type: DataTypes.DATE, - allowNull: true, - comment: 'Banner end display date' - }, - clickCount: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false - }, - impressions: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false - }, - targetAudience: { - type: DataTypes.ENUM('all', 'mobile', 'desktop'), - defaultValue: 'all', - allowNull: false - }, - backgroundColor: { - type: DataTypes.STRING, - allowNull: true, - validate: { - is: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/ - }, - comment: 'Hex color code for banner background' - }, - textColor: { - type: DataTypes.STRING, - allowNull: true, - validate: { - is: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/ - }, - comment: 'Hex color code for banner text' - }, - animation: { - type: DataTypes.ENUM('none', 'fade', 'slide', 'zoom'), - defaultValue: 'none', - allowNull: false - }, - metadata: { - type: DataTypes.JSONB, - allowNull: true, - defaultValue: {}, - comment: 'Additional banner metadata and settings' - } -}, { - tableName: 'banners', - timestamps: true, - paranoid: true, - indexes: [ - { - fields: ['isActive'] - }, - { - fields: ['position'] - }, - { - fields: ['order'] - }, - { - fields: ['startDate', 'endDate'] - } - ] -}); - -// Virtual field for checking if banner is currently active -Banner.prototype.isCurrentlyActive = function() { - if (!this.isActive) return false; - - const now = new Date(); - - if (this.startDate && now < this.startDate) return false; - if (this.endDate && now > this.endDate) return false; - - return true; -}; - -// Method to increment click count -Banner.prototype.recordClick = async function() { - this.clickCount += 1; - await this.save(); - return this; -}; - -// Method to increment impressions -Banner.prototype.recordImpression = async function() { - this.impressions += 1; - await this.save(); - return this; -}; - -// Static method to get active banners -Banner.getActiveBanners = async function(position = null) { - const whereClause = { - isActive: true - }; - - if (position) { - whereClause.position = position; - } - - const now = new Date(); - - return await this.findAll({ - where: { - ...whereClause, - [require('sequelize').Op.or]: [ - { startDate: null }, - { startDate: { [require('sequelize').Op.lte]: now } } - ], - [require('sequelize').Op.or]: [ - { endDate: null }, - { endDate: { [require('sequelize').Op.gte]: now } } - ] - }, - order: [['order', 'ASC'], ['createdAt', 'DESC']] - }); -}; - -module.exports = Banner; \ No newline at end of file diff --git a/.history/models/Banner_20251022195905.js b/.history/models/Banner_20251022195905.js deleted file mode 100644 index ab3dd28..0000000 --- a/.history/models/Banner_20251022195905.js +++ /dev/null @@ -1,195 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Banner = sequelize.define('Banner', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - title: { - type: DataTypes.STRING, - allowNull: false, - validate: { - notEmpty: true, - len: [1, 200] - } - }, - subtitle: { - type: DataTypes.STRING, - allowNull: true, - validate: { - len: [0, 300] - } - }, - description: { - type: DataTypes.TEXT, - allowNull: true - }, - buttonText: { - type: DataTypes.STRING, - allowNull: true, - validate: { - len: [0, 50] - } - }, - buttonUrl: { - type: DataTypes.STRING, - allowNull: true, - validate: { - isUrl: true - } - }, - image: { - type: DataTypes.STRING, - allowNull: true, - comment: 'Banner background image URL' - }, - mobileImage: { - type: DataTypes.STRING, - allowNull: true, - comment: 'Mobile-optimized banner image URL' - }, - position: { - type: DataTypes.ENUM('hero', 'secondary', 'footer'), - defaultValue: 'hero', - allowNull: false - }, - order: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - comment: 'Display order (lower numbers appear first)' - }, - isActive: { - type: DataTypes.BOOLEAN, - defaultValue: true, - allowNull: false - }, - startDate: { - type: DataTypes.DATE, - allowNull: true, - comment: 'Banner start display date' - }, - endDate: { - type: DataTypes.DATE, - allowNull: true, - comment: 'Banner end display date' - }, - clickCount: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false - }, - impressions: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false - }, - targetAudience: { - type: DataTypes.ENUM('all', 'mobile', 'desktop'), - defaultValue: 'all', - allowNull: false - }, - backgroundColor: { - type: DataTypes.STRING, - allowNull: true, - validate: { - is: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/ - }, - comment: 'Hex color code for banner background' - }, - textColor: { - type: DataTypes.STRING, - allowNull: true, - validate: { - is: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/ - }, - comment: 'Hex color code for banner text' - }, - animation: { - type: DataTypes.ENUM('none', 'fade', 'slide', 'zoom'), - defaultValue: 'none', - allowNull: false - }, - metadata: { - type: DataTypes.JSONB, - allowNull: true, - defaultValue: {}, - comment: 'Additional banner metadata and settings' - } -}, { - tableName: 'banners', - timestamps: true, - paranoid: true, - indexes: [ - { - fields: ['isActive'] - }, - { - fields: ['position'] - }, - { - fields: ['order'] - }, - { - fields: ['startDate', 'endDate'] - } - ] -}); - -// Virtual field for checking if banner is currently active -Banner.prototype.isCurrentlyActive = function() { - if (!this.isActive) return false; - - const now = new Date(); - - if (this.startDate && now < this.startDate) return false; - if (this.endDate && now > this.endDate) return false; - - return true; -}; - -// Method to increment click count -Banner.prototype.recordClick = async function() { - this.clickCount += 1; - await this.save(); - return this; -}; - -// Method to increment impressions -Banner.prototype.recordImpression = async function() { - this.impressions += 1; - await this.save(); - return this; -}; - -// Static method to get active banners -Banner.getActiveBanners = async function(position = null) { - const whereClause = { - isActive: true - }; - - if (position) { - whereClause.position = position; - } - - const now = new Date(); - - return await this.findAll({ - where: { - ...whereClause, - [require('sequelize').Op.or]: [ - { startDate: null }, - { startDate: { [require('sequelize').Op.lte]: now } } - ], - [require('sequelize').Op.or]: [ - { endDate: null }, - { endDate: { [require('sequelize').Op.gte]: now } } - ] - }, - order: [['order', 'ASC'], ['createdAt', 'DESC']] - }); -}; - -module.exports = Banner; \ No newline at end of file diff --git a/.history/models/Contact_20251019160612.js b/.history/models/Contact_20251019160612.js deleted file mode 100644 index c7a79ff..0000000 --- a/.history/models/Contact_20251019160612.js +++ /dev/null @@ -1,80 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/Contact_20251019162544.js b/.history/models/Contact_20251019162544.js deleted file mode 100644 index c7a79ff..0000000 --- a/.history/models/Contact_20251019162544.js +++ /dev/null @@ -1,80 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/Contact_20251019201845.js b/.history/models/Contact_20251019201845.js deleted file mode 100644 index be16e21..0000000 --- a/.history/models/Contact_20251019201845.js +++ /dev/null @@ -1,108 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Contact = sequelize.define('Contact', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: false, - set(value) { - this.setDataValue('name', value.trim()); - } - }, - email: { - type: DataTypes.STRING, - allowNull: false, - validate: { - isEmail: true - }, - set(value) { - this.setDataValue('email', value.toLowerCase().trim()); - } - }, - phone: { - type: DataTypes.STRING, - allowNull: true, - set(value) { - this.setDataValue('phone', value ? value.trim() : null); - } - }, - company: { - type: DataTypes.STRING, - allowNull: true, - set(value) { - this.setDataValue('company', value ? value.trim() : null); - } - }, - subject: { - type: DataTypes.STRING, - allowNull: false, - set(value) { - this.setDataValue('subject', value.trim()); - } - }, - message: { - type: DataTypes.TEXT, - allowNull: false - }, - serviceInterest: { - type: DataTypes.ENUM('web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other'), - allowNull: true - }, - budget: { - type: DataTypes.ENUM('under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m'), - allowNull: true - }, - timeline: { - type: DataTypes.ENUM('asap', '1-month', '1-3-months', '3-6-months', 'flexible'), - allowNull: true - }, - status: { - type: DataTypes.ENUM('new', 'in-progress', 'replied', 'closed'), - defaultValue: 'new' - }, - priority: { - type: DataTypes.ENUM('low', 'medium', 'high', 'urgent'), - defaultValue: 'medium' - }, - source: { - type: DataTypes.ENUM('website', 'telegram', 'email', 'phone', 'referral'), - defaultValue: 'website' - }, - isRead: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - adminNotes: { - type: DataTypes.TEXT, - allowNull: true - }, - ipAddress: { - type: DataTypes.STRING, - allowNull: true - }, - userAgent: { - type: DataTypes.TEXT, - allowNull: true - } -}, { - tableName: 'contacts', - timestamps: true, - indexes: [ - { - fields: ['status', 'createdAt'] - }, - { - fields: ['isRead', 'createdAt'] - }, - { - fields: ['email'] - } - ] -}); - -module.exports = Contact; \ No newline at end of file diff --git a/.history/models/Contact_20251019202631.js b/.history/models/Contact_20251019202631.js deleted file mode 100644 index be16e21..0000000 --- a/.history/models/Contact_20251019202631.js +++ /dev/null @@ -1,108 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Contact = sequelize.define('Contact', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: false, - set(value) { - this.setDataValue('name', value.trim()); - } - }, - email: { - type: DataTypes.STRING, - allowNull: false, - validate: { - isEmail: true - }, - set(value) { - this.setDataValue('email', value.toLowerCase().trim()); - } - }, - phone: { - type: DataTypes.STRING, - allowNull: true, - set(value) { - this.setDataValue('phone', value ? value.trim() : null); - } - }, - company: { - type: DataTypes.STRING, - allowNull: true, - set(value) { - this.setDataValue('company', value ? value.trim() : null); - } - }, - subject: { - type: DataTypes.STRING, - allowNull: false, - set(value) { - this.setDataValue('subject', value.trim()); - } - }, - message: { - type: DataTypes.TEXT, - allowNull: false - }, - serviceInterest: { - type: DataTypes.ENUM('web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other'), - allowNull: true - }, - budget: { - type: DataTypes.ENUM('under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m'), - allowNull: true - }, - timeline: { - type: DataTypes.ENUM('asap', '1-month', '1-3-months', '3-6-months', 'flexible'), - allowNull: true - }, - status: { - type: DataTypes.ENUM('new', 'in-progress', 'replied', 'closed'), - defaultValue: 'new' - }, - priority: { - type: DataTypes.ENUM('low', 'medium', 'high', 'urgent'), - defaultValue: 'medium' - }, - source: { - type: DataTypes.ENUM('website', 'telegram', 'email', 'phone', 'referral'), - defaultValue: 'website' - }, - isRead: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - adminNotes: { - type: DataTypes.TEXT, - allowNull: true - }, - ipAddress: { - type: DataTypes.STRING, - allowNull: true - }, - userAgent: { - type: DataTypes.TEXT, - allowNull: true - } -}, { - tableName: 'contacts', - timestamps: true, - indexes: [ - { - fields: ['status', 'createdAt'] - }, - { - fields: ['isRead', 'createdAt'] - }, - { - fields: ['email'] - } - ] -}); - -module.exports = Contact; \ No newline at end of file diff --git a/.history/models/Portfolio_20251019160550.js b/.history/models/Portfolio_20251019160550.js deleted file mode 100644 index d86985a..0000000 --- a/.history/models/Portfolio_20251019160550.js +++ /dev/null @@ -1,107 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/Portfolio_20251019162544.js b/.history/models/Portfolio_20251019162544.js deleted file mode 100644 index d86985a..0000000 --- a/.history/models/Portfolio_20251019162544.js +++ /dev/null @@ -1,107 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/Portfolio_20251019201803.js b/.history/models/Portfolio_20251019201803.js deleted file mode 100644 index 089f685..0000000 --- a/.history/models/Portfolio_20251019201803.js +++ /dev/null @@ -1,121 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Portfolio = sequelize.define('Portfolio', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - title: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [1, 255] - }, - set(value) { - this.setDataValue('title', value.trim()); - } - }, - description: { - type: DataTypes.TEXT, - allowNull: false - }, - shortDescription: { - type: DataTypes.STRING(200), - allowNull: false - }, - category: { - type: DataTypes.ENUM('web-development', 'mobile-app', 'ui-ux-design', 'branding', 'e-commerce', 'other'), - allowNull: false - }, - technologies: { - type: DataTypes.ARRAY(DataTypes.STRING), - defaultValue: [] - }, - images: { - type: DataTypes.JSONB, - defaultValue: [] - }, - clientName: { - type: DataTypes.STRING, - allowNull: true, - set(value) { - this.setDataValue('clientName', value ? value.trim() : null); - } - }, - projectUrl: { - type: DataTypes.STRING, - allowNull: true, - validate: { - isUrl: true - } - }, - githubUrl: { - type: DataTypes.STRING, - allowNull: true, - validate: { - isUrl: true - } - }, - status: { - type: DataTypes.ENUM('completed', 'in-progress', 'planning'), - defaultValue: 'completed' - }, - featured: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - publishedAt: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - }, - completedAt: { - type: DataTypes.DATE, - allowNull: true - }, - isPublished: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - viewCount: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - likes: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - order: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - seo: { - type: DataTypes.JSONB, - defaultValue: {} - } -}, { - tableName: 'portfolios', - timestamps: true, - indexes: [ - { - fields: ['category', 'publishedAt'] - }, - { - fields: ['featured', 'publishedAt'] - }, - { - type: 'gin', - fields: ['technologies'] - } - ] -}); - -// Virtual for primary image -Portfolio.prototype.getPrimaryImage = function() { - if (!this.images || this.images.length === 0) return null; - const primary = this.images.find(img => img.isPrimary); - return primary || this.images[0]; -}; - -module.exports = Portfolio; \ No newline at end of file diff --git a/.history/models/Portfolio_20251019202630.js b/.history/models/Portfolio_20251019202630.js deleted file mode 100644 index 089f685..0000000 --- a/.history/models/Portfolio_20251019202630.js +++ /dev/null @@ -1,121 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Portfolio = sequelize.define('Portfolio', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - title: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [1, 255] - }, - set(value) { - this.setDataValue('title', value.trim()); - } - }, - description: { - type: DataTypes.TEXT, - allowNull: false - }, - shortDescription: { - type: DataTypes.STRING(200), - allowNull: false - }, - category: { - type: DataTypes.ENUM('web-development', 'mobile-app', 'ui-ux-design', 'branding', 'e-commerce', 'other'), - allowNull: false - }, - technologies: { - type: DataTypes.ARRAY(DataTypes.STRING), - defaultValue: [] - }, - images: { - type: DataTypes.JSONB, - defaultValue: [] - }, - clientName: { - type: DataTypes.STRING, - allowNull: true, - set(value) { - this.setDataValue('clientName', value ? value.trim() : null); - } - }, - projectUrl: { - type: DataTypes.STRING, - allowNull: true, - validate: { - isUrl: true - } - }, - githubUrl: { - type: DataTypes.STRING, - allowNull: true, - validate: { - isUrl: true - } - }, - status: { - type: DataTypes.ENUM('completed', 'in-progress', 'planning'), - defaultValue: 'completed' - }, - featured: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - publishedAt: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - }, - completedAt: { - type: DataTypes.DATE, - allowNull: true - }, - isPublished: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - viewCount: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - likes: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - order: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - seo: { - type: DataTypes.JSONB, - defaultValue: {} - } -}, { - tableName: 'portfolios', - timestamps: true, - indexes: [ - { - fields: ['category', 'publishedAt'] - }, - { - fields: ['featured', 'publishedAt'] - }, - { - type: 'gin', - fields: ['technologies'] - } - ] -}); - -// Virtual for primary image -Portfolio.prototype.getPrimaryImage = function() { - if (!this.images || this.images.length === 0) return null; - const primary = this.images.find(img => img.isPrimary); - return primary || this.images[0]; -}; - -module.exports = Portfolio; \ No newline at end of file diff --git a/.history/models/Service_20251019160601.js b/.history/models/Service_20251019160601.js deleted file mode 100644 index d8f37e6..0000000 --- a/.history/models/Service_20251019160601.js +++ /dev/null @@ -1,102 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/Service_20251019162544.js b/.history/models/Service_20251019162544.js deleted file mode 100644 index d8f37e6..0000000 --- a/.history/models/Service_20251019162544.js +++ /dev/null @@ -1,102 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/Service_20251019201824.js b/.history/models/Service_20251019201824.js deleted file mode 100644 index 61a9098..0000000 --- a/.history/models/Service_20251019201824.js +++ /dev/null @@ -1,96 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Service = sequelize.define('Service', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [1, 255] - }, - set(value) { - this.setDataValue('name', value.trim()); - } - }, - description: { - type: DataTypes.TEXT, - allowNull: false - }, - shortDescription: { - type: DataTypes.STRING(150), - allowNull: false - }, - icon: { - type: DataTypes.STRING, - allowNull: false - }, - category: { - type: DataTypes.ENUM('development', 'design', 'consulting', 'marketing', 'maintenance'), - allowNull: false - }, - features: { - type: DataTypes.JSONB, - defaultValue: [] - }, - pricing: { - type: DataTypes.JSONB, - allowNull: false, - validate: { - isValidPricing(value) { - if (!value.basePrice || value.basePrice < 0) { - throw new Error('Base price must be a positive number'); - } - } - } - }, - estimatedTime: { - type: DataTypes.JSONB, - allowNull: false, - validate: { - isValidTime(value) { - if (!value.min || !value.max || value.min > value.max) { - throw new Error('Invalid estimated time range'); - } - } - } - }, - isActive: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - featured: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - order: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - tags: { - type: DataTypes.ARRAY(DataTypes.STRING), - defaultValue: [] - }, - seo: { - type: DataTypes.JSONB, - defaultValue: {} - } -}, { - tableName: 'services', - timestamps: true, - indexes: [ - { - fields: ['category', 'featured', 'order'] - }, - { - type: 'gin', - fields: ['tags'] - } - ] -}); - -module.exports = Service; \ No newline at end of file diff --git a/.history/models/Service_20251019202630.js b/.history/models/Service_20251019202630.js deleted file mode 100644 index 61a9098..0000000 --- a/.history/models/Service_20251019202630.js +++ /dev/null @@ -1,96 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const Service = sequelize.define('Service', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [1, 255] - }, - set(value) { - this.setDataValue('name', value.trim()); - } - }, - description: { - type: DataTypes.TEXT, - allowNull: false - }, - shortDescription: { - type: DataTypes.STRING(150), - allowNull: false - }, - icon: { - type: DataTypes.STRING, - allowNull: false - }, - category: { - type: DataTypes.ENUM('development', 'design', 'consulting', 'marketing', 'maintenance'), - allowNull: false - }, - features: { - type: DataTypes.JSONB, - defaultValue: [] - }, - pricing: { - type: DataTypes.JSONB, - allowNull: false, - validate: { - isValidPricing(value) { - if (!value.basePrice || value.basePrice < 0) { - throw new Error('Base price must be a positive number'); - } - } - } - }, - estimatedTime: { - type: DataTypes.JSONB, - allowNull: false, - validate: { - isValidTime(value) { - if (!value.min || !value.max || value.min > value.max) { - throw new Error('Invalid estimated time range'); - } - } - } - }, - isActive: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - featured: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - order: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - tags: { - type: DataTypes.ARRAY(DataTypes.STRING), - defaultValue: [] - }, - seo: { - type: DataTypes.JSONB, - defaultValue: {} - } -}, { - tableName: 'services', - timestamps: true, - indexes: [ - { - fields: ['category', 'featured', 'order'] - }, - { - type: 'gin', - fields: ['tags'] - } - ] -}); - -module.exports = Service; \ No newline at end of file diff --git a/.history/models/SiteSettings_20251019160626.js b/.history/models/SiteSettings_20251019160626.js deleted file mode 100644 index 0156ce7..0000000 --- a/.history/models/SiteSettings_20251019160626.js +++ /dev/null @@ -1,116 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/SiteSettings_20251019162544.js b/.history/models/SiteSettings_20251019162544.js deleted file mode 100644 index 0156ce7..0000000 --- a/.history/models/SiteSettings_20251019162544.js +++ /dev/null @@ -1,116 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/SiteSettings_20251019201906.js b/.history/models/SiteSettings_20251019201906.js deleted file mode 100644 index a8574c2..0000000 --- a/.history/models/SiteSettings_20251019201906.js +++ /dev/null @@ -1,82 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const SiteSettings = sequelize.define('SiteSettings', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - siteName: { - type: DataTypes.STRING, - defaultValue: 'SmartSolTech' - }, - siteDescription: { - type: DataTypes.TEXT, - defaultValue: 'Innovative technology solutions for modern businesses' - }, - logo: { - type: DataTypes.STRING, - defaultValue: '/images/logo.png' - }, - favicon: { - type: DataTypes.STRING, - defaultValue: '/images/favicon.ico' - }, - contact: { - type: DataTypes.JSONB, - defaultValue: { - email: 'info@smartsoltech.kr', - phone: '+82-10-0000-0000', - address: 'Seoul, South Korea' - } - }, - social: { - type: DataTypes.JSONB, - defaultValue: {} - }, - telegram: { - type: DataTypes.JSONB, - defaultValue: { - isEnabled: false - } - }, - seo: { - type: DataTypes.JSONB, - defaultValue: { - metaTitle: 'SmartSolTech - Technology Solutions', - metaDescription: 'Professional web development, mobile apps, and digital solutions in Korea', - keywords: 'web development, mobile apps, UI/UX design, Korea, technology' - } - }, - hero: { - type: DataTypes.JSONB, - defaultValue: { - title: 'Smart Technology Solutions', - subtitle: 'We create innovative digital experiences that drive business growth', - backgroundImage: '/images/hero-bg.jpg', - ctaText: 'Get Started', - ctaLink: '#contact' - } - }, - about: { - type: DataTypes.JSONB, - defaultValue: { - title: 'About SmartSolTech', - description: 'We are a team of passionate developers and designers creating cutting-edge technology solutions.', - image: '/images/about.jpg' - } - }, - maintenance: { - type: DataTypes.JSONB, - defaultValue: { - isEnabled: false, - message: 'We are currently performing maintenance. Please check back soon.' - } - } -}, { - tableName: 'site_settings', - timestamps: true -}); - -module.exports = SiteSettings; \ No newline at end of file diff --git a/.history/models/SiteSettings_20251019202631.js b/.history/models/SiteSettings_20251019202631.js deleted file mode 100644 index a8574c2..0000000 --- a/.history/models/SiteSettings_20251019202631.js +++ /dev/null @@ -1,82 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const SiteSettings = sequelize.define('SiteSettings', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - siteName: { - type: DataTypes.STRING, - defaultValue: 'SmartSolTech' - }, - siteDescription: { - type: DataTypes.TEXT, - defaultValue: 'Innovative technology solutions for modern businesses' - }, - logo: { - type: DataTypes.STRING, - defaultValue: '/images/logo.png' - }, - favicon: { - type: DataTypes.STRING, - defaultValue: '/images/favicon.ico' - }, - contact: { - type: DataTypes.JSONB, - defaultValue: { - email: 'info@smartsoltech.kr', - phone: '+82-10-0000-0000', - address: 'Seoul, South Korea' - } - }, - social: { - type: DataTypes.JSONB, - defaultValue: {} - }, - telegram: { - type: DataTypes.JSONB, - defaultValue: { - isEnabled: false - } - }, - seo: { - type: DataTypes.JSONB, - defaultValue: { - metaTitle: 'SmartSolTech - Technology Solutions', - metaDescription: 'Professional web development, mobile apps, and digital solutions in Korea', - keywords: 'web development, mobile apps, UI/UX design, Korea, technology' - } - }, - hero: { - type: DataTypes.JSONB, - defaultValue: { - title: 'Smart Technology Solutions', - subtitle: 'We create innovative digital experiences that drive business growth', - backgroundImage: '/images/hero-bg.jpg', - ctaText: 'Get Started', - ctaLink: '#contact' - } - }, - about: { - type: DataTypes.JSONB, - defaultValue: { - title: 'About SmartSolTech', - description: 'We are a team of passionate developers and designers creating cutting-edge technology solutions.', - image: '/images/about.jpg' - } - }, - maintenance: { - type: DataTypes.JSONB, - defaultValue: { - isEnabled: false, - message: 'We are currently performing maintenance. Please check back soon.' - } - } -}, { - tableName: 'site_settings', - timestamps: true -}); - -module.exports = SiteSettings; \ No newline at end of file diff --git a/.history/models/User_20251019160538.js b/.history/models/User_20251019160538.js deleted file mode 100644 index 27dc646..0000000 --- a/.history/models/User_20251019160538.js +++ /dev/null @@ -1,75 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/User_20251019162544.js b/.history/models/User_20251019162544.js deleted file mode 100644 index 27dc646..0000000 --- a/.history/models/User_20251019162544.js +++ /dev/null @@ -1,75 +0,0 @@ -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); \ No newline at end of file diff --git a/.history/models/User_20251019201741.js b/.history/models/User_20251019201741.js deleted file mode 100644 index 8643388..0000000 --- a/.history/models/User_20251019201741.js +++ /dev/null @@ -1,78 +0,0 @@ -const { DataTypes } = require('sequelize'); -const bcrypt = require('bcryptjs'); -const { sequelize } = require('../config/database'); - -const User = sequelize.define('User', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - email: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - validate: { - isEmail: true - }, - set(value) { - this.setDataValue('email', value.toLowerCase().trim()); - } - }, - password: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [6, 255] - } - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [1, 100] - }, - set(value) { - this.setDataValue('name', value.trim()); - } - }, - role: { - type: DataTypes.ENUM('admin', 'moderator'), - defaultValue: 'admin' - }, - avatar: { - type: DataTypes.STRING, - allowNull: true - }, - isActive: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - lastLogin: { - type: DataTypes.DATE, - allowNull: true - } -}, { - tableName: 'users', - timestamps: true, - hooks: { - beforeSave: async (user) => { - if (user.changed('password')) { - const salt = await bcrypt.genSalt(12); - user.password = await bcrypt.hash(user.password, salt); - } - } - } -}); - -// Instance methods -User.prototype.comparePassword = async function(candidatePassword) { - return bcrypt.compare(candidatePassword, this.password); -}; - -User.prototype.updateLastLogin = function() { - this.lastLogin = new Date(); - return this.save(); -}; - -module.exports = User; \ No newline at end of file diff --git a/.history/models/User_20251019202630.js b/.history/models/User_20251019202630.js deleted file mode 100644 index 8643388..0000000 --- a/.history/models/User_20251019202630.js +++ /dev/null @@ -1,78 +0,0 @@ -const { DataTypes } = require('sequelize'); -const bcrypt = require('bcryptjs'); -const { sequelize } = require('../config/database'); - -const User = sequelize.define('User', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - email: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - validate: { - isEmail: true - }, - set(value) { - this.setDataValue('email', value.toLowerCase().trim()); - } - }, - password: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [6, 255] - } - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [1, 100] - }, - set(value) { - this.setDataValue('name', value.trim()); - } - }, - role: { - type: DataTypes.ENUM('admin', 'moderator'), - defaultValue: 'admin' - }, - avatar: { - type: DataTypes.STRING, - allowNull: true - }, - isActive: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - lastLogin: { - type: DataTypes.DATE, - allowNull: true - } -}, { - tableName: 'users', - timestamps: true, - hooks: { - beforeSave: async (user) => { - if (user.changed('password')) { - const salt = await bcrypt.genSalt(12); - user.password = await bcrypt.hash(user.password, salt); - } - } - } -}); - -// Instance methods -User.prototype.comparePassword = async function(candidatePassword) { - return bcrypt.compare(candidatePassword, this.password); -}; - -User.prototype.updateLastLogin = function() { - this.lastLogin = new Date(); - return this.save(); -}; - -module.exports = User; \ No newline at end of file diff --git a/.history/models/index_20251019201914.js b/.history/models/index_20251019201914.js deleted file mode 100644 index 659182c..0000000 --- a/.history/models/index_20251019201914.js +++ /dev/null @@ -1,23 +0,0 @@ -const { sequelize } = require('../config/database'); - -// Import models -const User = require('./User'); -const Portfolio = require('./Portfolio'); -const Service = require('./Service'); -const Contact = require('./Contact'); -const SiteSettings = require('./SiteSettings'); - -// Define associations here if needed -// For example: -// Service.belongsToMany(Portfolio, { through: 'ServicePortfolio' }); -// Portfolio.belongsToMany(Service, { through: 'ServicePortfolio' }); - -// Export models and sequelize instance -module.exports = { - sequelize, - User, - Portfolio, - Service, - Contact, - SiteSettings -}; \ No newline at end of file diff --git a/.history/models/index_20251019202631.js b/.history/models/index_20251019202631.js deleted file mode 100644 index 659182c..0000000 --- a/.history/models/index_20251019202631.js +++ /dev/null @@ -1,23 +0,0 @@ -const { sequelize } = require('../config/database'); - -// Import models -const User = require('./User'); -const Portfolio = require('./Portfolio'); -const Service = require('./Service'); -const Contact = require('./Contact'); -const SiteSettings = require('./SiteSettings'); - -// Define associations here if needed -// For example: -// Service.belongsToMany(Portfolio, { through: 'ServicePortfolio' }); -// Portfolio.belongsToMany(Service, { through: 'ServicePortfolio' }); - -// Export models and sequelize instance -module.exports = { - sequelize, - User, - Portfolio, - Service, - Contact, - SiteSettings -}; \ No newline at end of file diff --git a/.history/models/index_20251022194816.js b/.history/models/index_20251022194816.js deleted file mode 100644 index 2a040ac..0000000 --- a/.history/models/index_20251022194816.js +++ /dev/null @@ -1,25 +0,0 @@ -const { sequelize } = require('../config/database'); - -// Import models -const User = require('./User'); -const Portfolio = require('./Portfolio'); -const Service = require('./Service'); -const Contact = require('./Contact'); -const SiteSettings = require('./SiteSettings'); -const Banner = require('./Banner'); - -// Define associations here if needed -// For example: -// Service.belongsToMany(Portfolio, { through: 'ServicePortfolio' }); -// Portfolio.belongsToMany(Service, { through: 'ServicePortfolio' }); - -// Export models and sequelize instance -module.exports = { - sequelize, - User, - Portfolio, - Service, - Contact, - SiteSettings, - Banner -}; \ No newline at end of file diff --git a/.history/models/index_20251022195905.js b/.history/models/index_20251022195905.js deleted file mode 100644 index 2a040ac..0000000 --- a/.history/models/index_20251022195905.js +++ /dev/null @@ -1,25 +0,0 @@ -const { sequelize } = require('../config/database'); - -// Import models -const User = require('./User'); -const Portfolio = require('./Portfolio'); -const Service = require('./Service'); -const Contact = require('./Contact'); -const SiteSettings = require('./SiteSettings'); -const Banner = require('./Banner'); - -// Define associations here if needed -// For example: -// Service.belongsToMany(Portfolio, { through: 'ServicePortfolio' }); -// Portfolio.belongsToMany(Service, { through: 'ServicePortfolio' }); - -// Export models and sequelize instance -module.exports = { - sequelize, - User, - Portfolio, - Service, - Contact, - SiteSettings, - Banner -}; \ No newline at end of file diff --git a/.history/package_20251019160453.json b/.history/package_20251019160453.json deleted file mode 100644 index 5a9baac..0000000 --- a/.history/package_20251019160453.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/package_20251019162544.json b/.history/package_20251019162544.json deleted file mode 100644 index 5a9baac..0000000 --- a/.history/package_20251019162544.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/package_20251019162621.json b/.history/package_20251019162621.json deleted file mode 100644 index 83a22c9..0000000 --- a/.history/package_20251019162621.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/package_20251019163806.json b/.history/package_20251019163806.json deleted file mode 100644 index 83a22c9..0000000 --- a/.history/package_20251019163806.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/package_20251019163809.json b/.history/package_20251019163809.json deleted file mode 100644 index 1ef301a..0000000 --- a/.history/package_20251019163809.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/package_20251019163858.json b/.history/package_20251019163858.json deleted file mode 100644 index 1ef301a..0000000 --- a/.history/package_20251019163858.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "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" - } -} \ No newline at end of file diff --git a/.history/package_20251021172116.json b/.history/package_20251021172116.json deleted file mode 100644 index 7b42816..0000000 --- a/.history/package_20251021172116.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "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", - "sync-locales": "node scripts/sync-locales.js" - }, - "keywords": [ - "pwa", - "nodejs", - "express", - "telegram", - "portfolio", - "calculator" - ], - "author": "SmartSolTech", - "license": "MIT", - "dependencies": { - "bcryptjs": "^2.4.3", - "compression": "^1.7.4", - "connect-flash": "^0.1.1", - "connect-session-sequelize": "^8.0.2", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-rate-limit": "^7.1.5", - "express-session": "^1.17.3", - "express-validator": "^7.0.1", - "helmet": "^7.1.0", - "i18n": "^0.15.2", - "jsonwebtoken": "^9.0.2", - "morgan": "^1.10.0", - "multer": "^1.4.5-lts.1", - "node-telegram-bot-api": "^0.64.0", - "nodemailer": "^6.9.7", - "pg": "^8.16.3", - "pg-hstore": "^2.3.4", - "sequelize": "^6.37.7", - "sharp": "^0.33.0", - "socket.io": "^4.7.4" - }, - "devDependencies": { - "css-loader": "^6.8.1", - "file-loader": "^6.2.0", - "html-webpack-plugin": "^5.5.3", - "mini-css-extract-plugin": "^2.7.6", - "nodemon": "^3.0.2", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.89.0", - "webpack-cli": "^5.1.4", - "workbox-webpack-plugin": "^7.0.0" - } -} diff --git a/.history/package_20251021172239.json b/.history/package_20251021172239.json deleted file mode 100644 index 7b42816..0000000 --- a/.history/package_20251021172239.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "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", - "sync-locales": "node scripts/sync-locales.js" - }, - "keywords": [ - "pwa", - "nodejs", - "express", - "telegram", - "portfolio", - "calculator" - ], - "author": "SmartSolTech", - "license": "MIT", - "dependencies": { - "bcryptjs": "^2.4.3", - "compression": "^1.7.4", - "connect-flash": "^0.1.1", - "connect-session-sequelize": "^8.0.2", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-rate-limit": "^7.1.5", - "express-session": "^1.17.3", - "express-validator": "^7.0.1", - "helmet": "^7.1.0", - "i18n": "^0.15.2", - "jsonwebtoken": "^9.0.2", - "morgan": "^1.10.0", - "multer": "^1.4.5-lts.1", - "node-telegram-bot-api": "^0.64.0", - "nodemailer": "^6.9.7", - "pg": "^8.16.3", - "pg-hstore": "^2.3.4", - "sequelize": "^6.37.7", - "sharp": "^0.33.0", - "socket.io": "^4.7.4" - }, - "devDependencies": { - "css-loader": "^6.8.1", - "file-loader": "^6.2.0", - "html-webpack-plugin": "^5.5.3", - "mini-css-extract-plugin": "^2.7.6", - "nodemon": "^3.0.2", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.89.0", - "webpack-cli": "^5.1.4", - "workbox-webpack-plugin": "^7.0.0" - } -} diff --git a/.history/package_20251022203957.json b/.history/package_20251022203957.json deleted file mode 100644 index 8db9954..0000000 --- a/.history/package_20251022203957.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "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", - "sync-locales": "node scripts/sync-locales.js" - }, - "keywords": [ - "pwa", - "nodejs", - "express", - "telegram", - "portfolio", - "calculator" - ], - "author": "SmartSolTech", - "license": "MIT", - "dependencies": { - "axios": "^1.12.2", - "bcryptjs": "^2.4.3", - "compression": "^1.7.4", - "connect-flash": "^0.1.1", - "connect-session-sequelize": "^7.1.7", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-ejs-layouts": "^2.5.1", - "express-rate-limit": "^7.1.5", - "express-session": "^1.17.3", - "express-validator": "^7.0.1", - "helmet": "^7.1.0", - "i18n": "^0.15.2", - "jsonwebtoken": "^9.0.2", - "morgan": "^1.10.0", - "multer": "^1.4.5-lts.1", - "node-telegram-bot-api": "^0.64.0", - "nodemailer": "^6.9.7", - "pg": "^8.16.3", - "pg-hstore": "^2.3.4", - "sequelize": "^6.37.7", - "sharp": "^0.33.5", - "socket.io": "^4.7.4" - }, - "devDependencies": { - "css-loader": "^6.8.1", - "file-loader": "^6.2.0", - "html-webpack-plugin": "^5.5.3", - "mini-css-extract-plugin": "^2.7.6", - "nodemon": "^3.0.2", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.89.0", - "webpack-cli": "^5.1.4", - "workbox-webpack-plugin": "^7.0.0" - } -} diff --git a/.history/package_20251022204114.json b/.history/package_20251022204114.json deleted file mode 100644 index 8db9954..0000000 --- a/.history/package_20251022204114.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "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", - "sync-locales": "node scripts/sync-locales.js" - }, - "keywords": [ - "pwa", - "nodejs", - "express", - "telegram", - "portfolio", - "calculator" - ], - "author": "SmartSolTech", - "license": "MIT", - "dependencies": { - "axios": "^1.12.2", - "bcryptjs": "^2.4.3", - "compression": "^1.7.4", - "connect-flash": "^0.1.1", - "connect-session-sequelize": "^7.1.7", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-ejs-layouts": "^2.5.1", - "express-rate-limit": "^7.1.5", - "express-session": "^1.17.3", - "express-validator": "^7.0.1", - "helmet": "^7.1.0", - "i18n": "^0.15.2", - "jsonwebtoken": "^9.0.2", - "morgan": "^1.10.0", - "multer": "^1.4.5-lts.1", - "node-telegram-bot-api": "^0.64.0", - "nodemailer": "^6.9.7", - "pg": "^8.16.3", - "pg-hstore": "^2.3.4", - "sequelize": "^6.37.7", - "sharp": "^0.33.5", - "socket.io": "^4.7.4" - }, - "devDependencies": { - "css-loader": "^6.8.1", - "file-loader": "^6.2.0", - "html-webpack-plugin": "^5.5.3", - "mini-css-extract-plugin": "^2.7.6", - "nodemon": "^3.0.2", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.3.9", - "webpack": "^5.89.0", - "webpack-cli": "^5.1.4", - "workbox-webpack-plugin": "^7.0.0" - } -} diff --git a/.history/public/css/base_20251020045405.css b/.history/public/css/base_20251020045405.css deleted file mode 100644 index cced8b5..0000000 --- a/.history/public/css/base_20251020045405.css +++ /dev/null @@ -1,325 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Hero Section */ -.hero-section { - min-height: 100vh !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; - color: white !important; - text-align: center !important; - padding: 2rem !important; -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/base_20251020045409.css b/.history/public/css/base_20251020045409.css deleted file mode 100644 index cced8b5..0000000 --- a/.history/public/css/base_20251020045409.css +++ /dev/null @@ -1,325 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Hero Section */ -.hero-section { - min-height: 100vh !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; - color: white !important; - text-align: center !important; - padding: 2rem !important; -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/base_20251020050032.css b/.history/public/css/base_20251020050032.css deleted file mode 100644 index 8bfbc24..0000000 --- a/.history/public/css/base_20251020050032.css +++ /dev/null @@ -1,590 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Base CSS - Принудительные стили для правильного отображения */ - -/* Reset и базовые стили */ -* { - box-sizing: border-box !important; -} - -html, body { - margin: 0 !important; - padding: 0 !important; - font-family: 'Inter', system-ui, -apple-system, sans-serif !important; - line-height: 1.6 !important; - color: #333 !important; - scroll-behavior: smooth !important; -} - -body { - padding-top: 80px !important; /* Отступ для фиксированной навигации */ -} - -/* Навигация */ -nav, .navigation { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - height: 80px !important; -} - -.navbar, .nav-container { - display: flex !important; - justify-content: space-between !important; - align-items: center !important; - padding: 1rem 2rem !important; - max-width: 1200px !important; - margin: 0 auto !important; - height: 100% !important; -} - -.logo, .brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #1d4ed8 !important; - text-decoration: none !important; -} - -.nav-links, .menu { - display: flex !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; - gap: 2rem !important; -} - -.nav-links a, .menu a { - color: #374151 !important; - text-decoration: none !important; - font-weight: 500 !important; - transition: color 0.3s ease !important; - padding: 0.5rem 0 !important; -} - -.nav-links a:hover, .menu a:hover { - color: #1d4ed8 !important; -} - -/* Hero секция */ -.hero-section { - min-height: 100vh !important; - background: linear-gradient(135deg, #1e3a8a 0%, #7c3aed 50%, #3730a3 100%) !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - text-align: center !important; - color: white !important; - position: relative !important; - overflow: hidden !important; - margin-top: -80px !important; /* Компенсация отступа */ - padding-top: 80px !important; -} - -.hero-section h1 { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1.5rem !important; - line-height: 1.2 !important; -} - -.hero-section p { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - opacity: 0.9 !important; -} - -/* Кнопки */ -.btn-primary, .btn { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - padding: 1rem 2rem !important; - background: linear-gradient(45deg, #3b82f6, #8b5cf6) !important; - color: white !important; - text-decoration: none !important; - border-radius: 50px !important; - font-weight: 600 !important; - transition: all 0.3s ease !important; - border: none !important; - cursor: pointer !important; - font-size: 1rem !important; -} - -.btn-primary:hover, .btn:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3) !important; -} - -/* Карточки */ -.card-hover, .card { - transition: all 0.3s ease !important; - border-radius: 1rem !important; - overflow: hidden !important; - background: white !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; -} - -.card-hover:hover, .card:hover { - transform: translateY(-8px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Секции */ -section { - padding: 5rem 1rem !important; -} - -.container, .max-w-7xl { - max-width: 1200px !important; - margin: 0 auto !important; - padding-left: 1rem !important; - padding-right: 1rem !important; -} - -/* Грид системы */ -.grid { - display: grid !important; -} - -.grid-cols-1 { - grid-template-columns: 1fr !important; -} - -.grid-cols-2 { - grid-template-columns: repeat(2, 1fr) !important; -} - -.grid-cols-3 { - grid-template-columns: repeat(3, 1fr) !important; -} - -.grid-cols-4 { - grid-template-columns: repeat(4, 1fr) !important; -} - -.gap-8 { - gap: 2rem !important; -} - -/* Заголовки */ -h1, h2, h3, h4, h5, h6 { - color: #1f2937 !important; - font-weight: 600 !important; -} - -h1 { - font-size: 3rem !important; -} - -h2 { - font-size: 2.5rem !important; -} - -h3 { - font-size: 1.75rem !important; -} - -/* Footer */ -footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 1rem 1rem !important; -} - -/* Отзывчивость */ -@media (max-width: 768px) { - .nav-links, .menu { - display: none !important; - } - - body { - padding-top: 60px !important; - } - - nav, .navigation { - height: 60px !important; - } - - .hero-section { - margin-top: -60px !important; - padding-top: 60px !important; - } - - section { - padding: 3rem 1rem !important; - } - - .hero-section h1 { - font-size: 2.5rem !important; - } - - .grid-cols-2, .grid-cols-3, .grid-cols-4 { - grid-template-columns: 1fr !important; - } -} - -/* Дополнительные утилитарные классы */ -.text-center { - text-align: center !important; -} - -.text-white { - color: white !important; -} - -.bg-white { - background-color: white !important; -} - -.rounded-lg { - border-radius: 0.5rem !important; -} - -.rounded-2xl { - border-radius: 1rem !important; -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; -} - -.mb-4 { - margin-bottom: 1rem !important; -} - -.mb-8 { - margin-bottom: 2rem !important; -} - -.p-8 { - padding: 2rem !important; -} - -.flex { - display: flex !important; -} - -.items-center { - align-items: center !important; -} - -.justify-center { - justify-content: center !important; -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/base_20251020050046.css b/.history/public/css/base_20251020050046.css deleted file mode 100644 index c9eacf9..0000000 --- a/.history/public/css/base_20251020050046.css +++ /dev/null @@ -1,591 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Base CSS - Принудительные стили для правильного отображения */ - -/* Reset и базовые стили */ -* { - box-sizing: border-box !important; -} - -html, body { - margin: 0 !important; - padding: 0 !important; - font-family: 'Inter', system-ui, -apple-system, sans-serif !important; - line-height: 1.6 !important; - color: #333 !important; - scroll-behavior: smooth !important; -} - -body { - padding-top: 80px !important; /* Отступ для фиксированной навигации */ -} - -/* Навигация */ -nav, .navigation { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - -webkit-backdrop-filter: blur(10px) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - height: 80px !important; -} - -.navbar, .nav-container { - display: flex !important; - justify-content: space-between !important; - align-items: center !important; - padding: 1rem 2rem !important; - max-width: 1200px !important; - margin: 0 auto !important; - height: 100% !important; -} - -.logo, .brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #1d4ed8 !important; - text-decoration: none !important; -} - -.nav-links, .menu { - display: flex !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; - gap: 2rem !important; -} - -.nav-links a, .menu a { - color: #374151 !important; - text-decoration: none !important; - font-weight: 500 !important; - transition: color 0.3s ease !important; - padding: 0.5rem 0 !important; -} - -.nav-links a:hover, .menu a:hover { - color: #1d4ed8 !important; -} - -/* Hero секция */ -.hero-section { - min-height: 100vh !important; - background: linear-gradient(135deg, #1e3a8a 0%, #7c3aed 50%, #3730a3 100%) !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - text-align: center !important; - color: white !important; - position: relative !important; - overflow: hidden !important; - margin-top: -80px !important; /* Компенсация отступа */ - padding-top: 80px !important; -} - -.hero-section h1 { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1.5rem !important; - line-height: 1.2 !important; -} - -.hero-section p { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - opacity: 0.9 !important; -} - -/* Кнопки */ -.btn-primary, .btn { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - padding: 1rem 2rem !important; - background: linear-gradient(45deg, #3b82f6, #8b5cf6) !important; - color: white !important; - text-decoration: none !important; - border-radius: 50px !important; - font-weight: 600 !important; - transition: all 0.3s ease !important; - border: none !important; - cursor: pointer !important; - font-size: 1rem !important; -} - -.btn-primary:hover, .btn:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3) !important; -} - -/* Карточки */ -.card-hover, .card { - transition: all 0.3s ease !important; - border-radius: 1rem !important; - overflow: hidden !important; - background: white !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; -} - -.card-hover:hover, .card:hover { - transform: translateY(-8px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Секции */ -section { - padding: 5rem 1rem !important; -} - -.container, .max-w-7xl { - max-width: 1200px !important; - margin: 0 auto !important; - padding-left: 1rem !important; - padding-right: 1rem !important; -} - -/* Грид системы */ -.grid { - display: grid !important; -} - -.grid-cols-1 { - grid-template-columns: 1fr !important; -} - -.grid-cols-2 { - grid-template-columns: repeat(2, 1fr) !important; -} - -.grid-cols-3 { - grid-template-columns: repeat(3, 1fr) !important; -} - -.grid-cols-4 { - grid-template-columns: repeat(4, 1fr) !important; -} - -.gap-8 { - gap: 2rem !important; -} - -/* Заголовки */ -h1, h2, h3, h4, h5, h6 { - color: #1f2937 !important; - font-weight: 600 !important; -} - -h1 { - font-size: 3rem !important; -} - -h2 { - font-size: 2.5rem !important; -} - -h3 { - font-size: 1.75rem !important; -} - -/* Footer */ -footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 1rem 1rem !important; -} - -/* Отзывчивость */ -@media (max-width: 768px) { - .nav-links, .menu { - display: none !important; - } - - body { - padding-top: 60px !important; - } - - nav, .navigation { - height: 60px !important; - } - - .hero-section { - margin-top: -60px !important; - padding-top: 60px !important; - } - - section { - padding: 3rem 1rem !important; - } - - .hero-section h1 { - font-size: 2.5rem !important; - } - - .grid-cols-2, .grid-cols-3, .grid-cols-4 { - grid-template-columns: 1fr !important; - } -} - -/* Дополнительные утилитарные классы */ -.text-center { - text-align: center !important; -} - -.text-white { - color: white !important; -} - -.bg-white { - background-color: white !important; -} - -.rounded-lg { - border-radius: 0.5rem !important; -} - -.rounded-2xl { - border-radius: 1rem !important; -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; -} - -.mb-4 { - margin-bottom: 1rem !important; -} - -.mb-8 { - margin-bottom: 2rem !important; -} - -.p-8 { - padding: 2rem !important; -} - -.flex { - display: flex !important; -} - -.items-center { - align-items: center !important; -} - -.justify-center { - justify-content: center !important; -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/base_20251020050055.css b/.history/public/css/base_20251020050055.css deleted file mode 100644 index c9eacf9..0000000 --- a/.history/public/css/base_20251020050055.css +++ /dev/null @@ -1,591 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Base CSS - Принудительные стили для правильного отображения */ - -/* Reset и базовые стили */ -* { - box-sizing: border-box !important; -} - -html, body { - margin: 0 !important; - padding: 0 !important; - font-family: 'Inter', system-ui, -apple-system, sans-serif !important; - line-height: 1.6 !important; - color: #333 !important; - scroll-behavior: smooth !important; -} - -body { - padding-top: 80px !important; /* Отступ для фиксированной навигации */ -} - -/* Навигация */ -nav, .navigation { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - -webkit-backdrop-filter: blur(10px) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - height: 80px !important; -} - -.navbar, .nav-container { - display: flex !important; - justify-content: space-between !important; - align-items: center !important; - padding: 1rem 2rem !important; - max-width: 1200px !important; - margin: 0 auto !important; - height: 100% !important; -} - -.logo, .brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #1d4ed8 !important; - text-decoration: none !important; -} - -.nav-links, .menu { - display: flex !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; - gap: 2rem !important; -} - -.nav-links a, .menu a { - color: #374151 !important; - text-decoration: none !important; - font-weight: 500 !important; - transition: color 0.3s ease !important; - padding: 0.5rem 0 !important; -} - -.nav-links a:hover, .menu a:hover { - color: #1d4ed8 !important; -} - -/* Hero секция */ -.hero-section { - min-height: 100vh !important; - background: linear-gradient(135deg, #1e3a8a 0%, #7c3aed 50%, #3730a3 100%) !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - text-align: center !important; - color: white !important; - position: relative !important; - overflow: hidden !important; - margin-top: -80px !important; /* Компенсация отступа */ - padding-top: 80px !important; -} - -.hero-section h1 { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1.5rem !important; - line-height: 1.2 !important; -} - -.hero-section p { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - opacity: 0.9 !important; -} - -/* Кнопки */ -.btn-primary, .btn { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - padding: 1rem 2rem !important; - background: linear-gradient(45deg, #3b82f6, #8b5cf6) !important; - color: white !important; - text-decoration: none !important; - border-radius: 50px !important; - font-weight: 600 !important; - transition: all 0.3s ease !important; - border: none !important; - cursor: pointer !important; - font-size: 1rem !important; -} - -.btn-primary:hover, .btn:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 30px rgba(59, 130, 246, 0.3) !important; -} - -/* Карточки */ -.card-hover, .card { - transition: all 0.3s ease !important; - border-radius: 1rem !important; - overflow: hidden !important; - background: white !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; -} - -.card-hover:hover, .card:hover { - transform: translateY(-8px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Секции */ -section { - padding: 5rem 1rem !important; -} - -.container, .max-w-7xl { - max-width: 1200px !important; - margin: 0 auto !important; - padding-left: 1rem !important; - padding-right: 1rem !important; -} - -/* Грид системы */ -.grid { - display: grid !important; -} - -.grid-cols-1 { - grid-template-columns: 1fr !important; -} - -.grid-cols-2 { - grid-template-columns: repeat(2, 1fr) !important; -} - -.grid-cols-3 { - grid-template-columns: repeat(3, 1fr) !important; -} - -.grid-cols-4 { - grid-template-columns: repeat(4, 1fr) !important; -} - -.gap-8 { - gap: 2rem !important; -} - -/* Заголовки */ -h1, h2, h3, h4, h5, h6 { - color: #1f2937 !important; - font-weight: 600 !important; -} - -h1 { - font-size: 3rem !important; -} - -h2 { - font-size: 2.5rem !important; -} - -h3 { - font-size: 1.75rem !important; -} - -/* Footer */ -footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 1rem 1rem !important; -} - -/* Отзывчивость */ -@media (max-width: 768px) { - .nav-links, .menu { - display: none !important; - } - - body { - padding-top: 60px !important; - } - - nav, .navigation { - height: 60px !important; - } - - .hero-section { - margin-top: -60px !important; - padding-top: 60px !important; - } - - section { - padding: 3rem 1rem !important; - } - - .hero-section h1 { - font-size: 2.5rem !important; - } - - .grid-cols-2, .grid-cols-3, .grid-cols-4 { - grid-template-columns: 1fr !important; - } -} - -/* Дополнительные утилитарные классы */ -.text-center { - text-align: center !important; -} - -.text-white { - color: white !important; -} - -.bg-white { - background-color: white !important; -} - -.rounded-lg { - border-radius: 0.5rem !important; -} - -.rounded-2xl { - border-radius: 1rem !important; -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; -} - -.mb-4 { - margin-bottom: 1rem !important; -} - -.mb-8 { - margin-bottom: 2rem !important; -} - -.p-8 { - padding: 2rem !important; -} - -.flex { - display: flex !important; -} - -.items-center { - align-items: center !important; -} - -.justify-center { - justify-content: center !important; -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/base_20251020225302.css b/.history/public/css/base_20251020225302.css deleted file mode 100644 index 0df34a4..0000000 --- a/.history/public/css/base_20251020225302.css +++ /dev/null @@ -1,686 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Base CSS - Исправленные стили для главной страницы */ - -/* Reset и базовые стили */ -* { - box-sizing: border-box !important; - margin: 0 !important; - padding: 0 !important; -} - -html { - font-size: 16px !important; - scroll-behavior: smooth !important; -} - -body { - font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; - line-height: 1.6 !important; - color: #1f2937 !important; - background-color: #ffffff !important; - margin: 0 !important; - padding: 0 !important; - padding-top: 80px !important; /* Отступ для навигации */ -} - -/* Навигация - исправленная */ -nav, .navigation { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - -webkit-backdrop-filter: blur(20px) !important; - backdrop-filter: blur(20px) !important; - border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; - height: 80px !important; - box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1) !important; -} - -.navbar, .nav-container { - display: flex !important; - justify-content: space-between !important; - align-items: center !important; - padding: 0 2rem !important; - max-width: 1200px !important; - margin: 0 auto !important; - height: 100% !important; -} - -.logo, .brand { - font-size: 1.75rem !important; - font-weight: 800 !important; - color: #1d4ed8 !important; - text-decoration: none !important; - letter-spacing: -0.025em !important; -} - -.nav-links, .menu { - display: flex !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; - gap: 2.5rem !important; -} - -.nav-links li, .menu li { - margin: 0 !important; - padding: 0 !important; -} - -.nav-links a, .menu a { - color: #374151 !important; - text-decoration: none !important; - font-weight: 500 !important; - font-size: 0.95rem !important; - transition: all 0.3s ease !important; - padding: 0.75rem 0 !important; - position: relative !important; -} - -.nav-links a:hover, .menu a:hover { - color: #1d4ed8 !important; - transform: translateY(-1px) !important; -} - -.nav-links a::after, .menu a::after { - content: '' !important; - position: absolute !important; - bottom: 0.5rem !important; - left: 0 !important; - width: 0 !important; - height: 2px !important; - background: linear-gradient(45deg, #1d4ed8, #8b5cf6) !important; - transition: width 0.3s ease !important; -} - -.nav-links a:hover::after, .menu a:hover::after { - width: 100% !important; -} - -/* Hero секция - исправленная */ -.hero-section { - min-height: 100vh !important; - background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 25%, #312e81 50%, #7c3aed 75%, #3730a3 100%) !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - text-align: center !important; - color: white !important; - position: relative !important; - overflow: hidden !important; - margin-top: -80px !important; - padding-top: 80px !important; -} - -.hero-section::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - background: radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.3) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.3) 0%, transparent 50%), - radial-gradient(circle at 40% 80%, rgba(16, 185, 129, 0.2) 0%, transparent 50%) !important; - z-index: 1 !important; -} - -.hero-section > div { - position: relative !important; - z-index: 2 !important; -} - -.hero-section h1 { - font-size: 4rem !important; - font-weight: 800 !important; - margin-bottom: 1.5rem !important; - line-height: 1.1 !important; - letter-spacing: -0.025em !important; -} - -.hero-section p { - font-size: 1.35rem !important; - margin-bottom: 2.5rem !important; - opacity: 0.9 !important; - max-width: 600px !important; - margin-left: auto !important; - margin-right: auto !important; -} - -/* Кнопки - улучшенные */ -.btn-primary, .btn { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - padding: 1.25rem 2.5rem !important; - background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%) !important; - color: white !important; - text-decoration: none !important; - border-radius: 50px !important; - font-weight: 700 !important; - font-size: 1.1rem !important; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; - border: none !important; - cursor: pointer !important; - box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3) !important; - position: relative !important; - overflow: hidden !important; -} - -.btn-primary:hover, .btn:hover { - transform: translateY(-3px) scale(1.05) !important; - box-shadow: 0 15px 35px rgba(59, 130, 246, 0.4) !important; - background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%) !important; -} - -.btn-primary::before, .btn::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: -100% !important; - width: 100% !important; - height: 100% !important; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent) !important; - transition: left 0.6s !important; -} - -.btn-primary:hover::before, .btn:hover::before { - left: 100% !important; -} - -/* Карточки - улучшенные */ -.card-hover, .card { - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; - border-radius: 1.5rem !important; - overflow: hidden !important; - background: white !important; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05) !important; - border: 1px solid rgba(0, 0, 0, 0.05) !important; -} - -.card-hover:hover, .card:hover { - transform: translateY(-12px) scale(1.02) !important; - box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15) !important; - border-color: rgba(59, 130, 246, 0.2) !important; -} - -/* Секции */ -section { - padding: 6rem 1rem !important; - position: relative !important; -} - -.container, .max-w-7xl { - max-width: 1200px !important; - margin: 0 auto !important; - padding-left: 1rem !important; - padding-right: 1rem !important; -} - -/* Заголовки */ -h1, h2, h3, h4, h5, h6 { - color: #1f2937 !important; - font-weight: 700 !important; - line-height: 1.2 !important; -} - -h2 { - font-size: 2.75rem !important; - margin-bottom: 1rem !important; -} - -h3 { - font-size: 1.5rem !important; - margin-bottom: 0.75rem !important; -} - -/* Параграфы и текст */ -p { - color: #6b7280 !important; - line-height: 1.7 !important; - margin-bottom: 1rem !important; -} - -/* Утилитарные классы Tailwind - принудительные */ -.text-center { - text-align: center !important; -} - -.text-white { - color: white !important; -} - -.bg-white { - background-color: white !important; -} - -.bg-gray-50 { - background-color: #f9fafb !important; -} - -.rounded-lg { - border-radius: 0.5rem !important; -} - -.rounded-2xl { - border-radius: 1rem !important; -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; -} - -.mb-4 { - margin-bottom: 1rem !important; -} - -.mb-8 { - margin-bottom: 2rem !important; -} - -.p-8 { - padding: 2rem !important; -} - -.flex { - display: flex !important; -} - -.grid { - display: grid !important; -} - -.items-center { - align-items: center !important; -} - -.justify-center { - justify-content: center !important; -} - -/* Грид системы */ -.grid-cols-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)) !important; -} - -.grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)) !important; -} - -.grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)) !important; -} - -.grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)) !important; -} - -.gap-8 { - gap: 2rem !important; -} - -/* Отзывчивость - улучшенная */ -@media (max-width: 1024px) { - .grid-cols-4 { - grid-template-columns: repeat(2, minmax(0, 1fr)) !important; - } - - .hero-section h1 { - font-size: 3.5rem !important; - } -} - -@media (max-width: 768px) { - .nav-links, .menu { - display: none !important; - } - - body { - padding-top: 60px !important; - } - - nav, .navigation { - height: 60px !important; - } - - .hero-section { - margin-top: -60px !important; - padding-top: 60px !important; - } - - section { - padding: 4rem 1rem !important; - } - - .hero-section h1 { - font-size: 2.75rem !important; - } - - .hero-section p { - font-size: 1.1rem !important; - } - - .grid-cols-2, .grid-cols-3, .grid-cols-4 { - grid-template-columns: repeat(1, minmax(0, 1fr)) !important; - } - - .btn-primary, .btn { - padding: 1rem 2rem !important; - font-size: 1rem !important; - } -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/base_20251020225419.css b/.history/public/css/base_20251020225419.css deleted file mode 100644 index 0df34a4..0000000 --- a/.history/public/css/base_20251020225419.css +++ /dev/null @@ -1,686 +0,0 @@ -/* SmartSolTech - Base Styles for Elements */ - -/* Ensure proper styling without Tailwind conflicts */ -.navbar { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(10px) !important; - border-bottom: 1px solid #e5e7eb !important; - padding: 1rem 0 !important; -} - -.navbar-brand { - font-size: 1.5rem !important; - font-weight: 700 !important; - color: #3B82F6 !important; - text-decoration: none !important; -} - -.navbar-nav { - display: flex !important; - align-items: center !important; - gap: 2rem !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; -} - -.nav-link { - color: #6b7280 !important; - text-decoration: none !important; - font-weight: 500 !important; - padding: 0.5rem 1rem !important; - border-radius: 0.5rem !important; - transition: all 0.3s ease !important; -} - -.nav-link:hover, -.nav-link.active { - color: #3B82F6 !important; - background-color: #eff6ff !important; -} - -/* Base CSS - Исправленные стили для главной страницы */ - -/* Reset и базовые стили */ -* { - box-sizing: border-box !important; - margin: 0 !important; - padding: 0 !important; -} - -html { - font-size: 16px !important; - scroll-behavior: smooth !important; -} - -body { - font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; - line-height: 1.6 !important; - color: #1f2937 !important; - background-color: #ffffff !important; - margin: 0 !important; - padding: 0 !important; - padding-top: 80px !important; /* Отступ для навигации */ -} - -/* Навигация - исправленная */ -nav, .navigation { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1000 !important; - background: rgba(255, 255, 255, 0.95) !important; - -webkit-backdrop-filter: blur(20px) !important; - backdrop-filter: blur(20px) !important; - border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; - height: 80px !important; - box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1) !important; -} - -.navbar, .nav-container { - display: flex !important; - justify-content: space-between !important; - align-items: center !important; - padding: 0 2rem !important; - max-width: 1200px !important; - margin: 0 auto !important; - height: 100% !important; -} - -.logo, .brand { - font-size: 1.75rem !important; - font-weight: 800 !important; - color: #1d4ed8 !important; - text-decoration: none !important; - letter-spacing: -0.025em !important; -} - -.nav-links, .menu { - display: flex !important; - list-style: none !important; - margin: 0 !important; - padding: 0 !important; - gap: 2.5rem !important; -} - -.nav-links li, .menu li { - margin: 0 !important; - padding: 0 !important; -} - -.nav-links a, .menu a { - color: #374151 !important; - text-decoration: none !important; - font-weight: 500 !important; - font-size: 0.95rem !important; - transition: all 0.3s ease !important; - padding: 0.75rem 0 !important; - position: relative !important; -} - -.nav-links a:hover, .menu a:hover { - color: #1d4ed8 !important; - transform: translateY(-1px) !important; -} - -.nav-links a::after, .menu a::after { - content: '' !important; - position: absolute !important; - bottom: 0.5rem !important; - left: 0 !important; - width: 0 !important; - height: 2px !important; - background: linear-gradient(45deg, #1d4ed8, #8b5cf6) !important; - transition: width 0.3s ease !important; -} - -.nav-links a:hover::after, .menu a:hover::after { - width: 100% !important; -} - -/* Hero секция - исправленная */ -.hero-section { - min-height: 100vh !important; - background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 25%, #312e81 50%, #7c3aed 75%, #3730a3 100%) !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - text-align: center !important; - color: white !important; - position: relative !important; - overflow: hidden !important; - margin-top: -80px !important; - padding-top: 80px !important; -} - -.hero-section::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - background: radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.3) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.3) 0%, transparent 50%), - radial-gradient(circle at 40% 80%, rgba(16, 185, 129, 0.2) 0%, transparent 50%) !important; - z-index: 1 !important; -} - -.hero-section > div { - position: relative !important; - z-index: 2 !important; -} - -.hero-section h1 { - font-size: 4rem !important; - font-weight: 800 !important; - margin-bottom: 1.5rem !important; - line-height: 1.1 !important; - letter-spacing: -0.025em !important; -} - -.hero-section p { - font-size: 1.35rem !important; - margin-bottom: 2.5rem !important; - opacity: 0.9 !important; - max-width: 600px !important; - margin-left: auto !important; - margin-right: auto !important; -} - -/* Кнопки - улучшенные */ -.btn-primary, .btn { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - padding: 1.25rem 2.5rem !important; - background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%) !important; - color: white !important; - text-decoration: none !important; - border-radius: 50px !important; - font-weight: 700 !important; - font-size: 1.1rem !important; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; - border: none !important; - cursor: pointer !important; - box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3) !important; - position: relative !important; - overflow: hidden !important; -} - -.btn-primary:hover, .btn:hover { - transform: translateY(-3px) scale(1.05) !important; - box-shadow: 0 15px 35px rgba(59, 130, 246, 0.4) !important; - background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%) !important; -} - -.btn-primary::before, .btn::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: -100% !important; - width: 100% !important; - height: 100% !important; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent) !important; - transition: left 0.6s !important; -} - -.btn-primary:hover::before, .btn:hover::before { - left: 100% !important; -} - -/* Карточки - улучшенные */ -.card-hover, .card { - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; - border-radius: 1.5rem !important; - overflow: hidden !important; - background: white !important; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05) !important; - border: 1px solid rgba(0, 0, 0, 0.05) !important; -} - -.card-hover:hover, .card:hover { - transform: translateY(-12px) scale(1.02) !important; - box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15) !important; - border-color: rgba(59, 130, 246, 0.2) !important; -} - -/* Секции */ -section { - padding: 6rem 1rem !important; - position: relative !important; -} - -.container, .max-w-7xl { - max-width: 1200px !important; - margin: 0 auto !important; - padding-left: 1rem !important; - padding-right: 1rem !important; -} - -/* Заголовки */ -h1, h2, h3, h4, h5, h6 { - color: #1f2937 !important; - font-weight: 700 !important; - line-height: 1.2 !important; -} - -h2 { - font-size: 2.75rem !important; - margin-bottom: 1rem !important; -} - -h3 { - font-size: 1.5rem !important; - margin-bottom: 0.75rem !important; -} - -/* Параграфы и текст */ -p { - color: #6b7280 !important; - line-height: 1.7 !important; - margin-bottom: 1rem !important; -} - -/* Утилитарные классы Tailwind - принудительные */ -.text-center { - text-align: center !important; -} - -.text-white { - color: white !important; -} - -.bg-white { - background-color: white !important; -} - -.bg-gray-50 { - background-color: #f9fafb !important; -} - -.rounded-lg { - border-radius: 0.5rem !important; -} - -.rounded-2xl { - border-radius: 1rem !important; -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; -} - -.mb-4 { - margin-bottom: 1rem !important; -} - -.mb-8 { - margin-bottom: 2rem !important; -} - -.p-8 { - padding: 2rem !important; -} - -.flex { - display: flex !important; -} - -.grid { - display: grid !important; -} - -.items-center { - align-items: center !important; -} - -.justify-center { - justify-content: center !important; -} - -/* Грид системы */ -.grid-cols-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)) !important; -} - -.grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)) !important; -} - -.grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)) !important; -} - -.grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)) !important; -} - -.gap-8 { - gap: 2rem !important; -} - -/* Отзывчивость - улучшенная */ -@media (max-width: 1024px) { - .grid-cols-4 { - grid-template-columns: repeat(2, minmax(0, 1fr)) !important; - } - - .hero-section h1 { - font-size: 3.5rem !important; - } -} - -@media (max-width: 768px) { - .nav-links, .menu { - display: none !important; - } - - body { - padding-top: 60px !important; - } - - nav, .navigation { - height: 60px !important; - } - - .hero-section { - margin-top: -60px !important; - padding-top: 60px !important; - } - - section { - padding: 4rem 1rem !important; - } - - .hero-section h1 { - font-size: 2.75rem !important; - } - - .hero-section p { - font-size: 1.1rem !important; - } - - .grid-cols-2, .grid-cols-3, .grid-cols-4 { - grid-template-columns: repeat(1, minmax(0, 1fr)) !important; - } - - .btn-primary, .btn { - padding: 1rem 2rem !important; - font-size: 1rem !important; - } -} - -.hero-title { - font-size: 3.5rem !important; - font-weight: 700 !important; - margin-bottom: 1rem !important; - line-height: 1.2 !important; -} - -.hero-subtitle { - font-size: 1.25rem !important; - margin-bottom: 2rem !important; - color: rgba(255, 255, 255, 0.9) !important; -} - -/* Buttons */ -.btn { - display: inline-block !important; - padding: 0.75rem 1.5rem !important; - border-radius: 0.5rem !important; - text-decoration: none !important; - font-weight: 600 !important; - text-align: center !important; - border: none !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - font-size: 1rem !important; -} - -.btn-primary { - background: linear-gradient(135deg, #3B82F6, #1D4ED8) !important; - color: white !important; -} - -.btn-primary:hover { - transform: translateY(-2px) !important; - box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3) !important; -} - -.btn-secondary { - background: transparent !important; - color: white !important; - border: 2px solid white !important; -} - -.btn-secondary:hover { - background: white !important; - color: #3B82F6 !important; -} - -/* Cards */ -.card { - background: white !important; - border-radius: 1rem !important; - padding: 2rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.card:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -/* Sections */ -.section { - padding: 4rem 2rem !important; -} - -.section-title { - font-size: 2.5rem !important; - font-weight: 700 !important; - text-align: center !important; - margin-bottom: 1rem !important; - color: #1f2937 !important; -} - -.section-description { - font-size: 1.125rem !important; - text-align: center !important; - color: #6b7280 !important; - max-width: 600px !important; - margin: 0 auto 3rem !important; -} - -/* Container */ -.container { - max-width: 1200px !important; - margin: 0 auto !important; - padding: 0 1rem !important; -} - -/* Grid Layouts */ -.grid-2 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-3 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important; - gap: 2rem !important; -} - -.grid-4 { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; - gap: 1.5rem !important; -} - -/* Language Selector */ -.language-selector { - display: flex !important; - align-items: center !important; - gap: 0.5rem !important; -} - -.language-selector a { - color: #6b7280 !important; - text-decoration: none !important; - padding: 0.25rem 0.5rem !important; - border-radius: 0.25rem !important; - font-size: 0.875rem !important; -} - -.language-selector a:hover { - color: #3B82F6 !important; - background-color: #f3f4f6 !important; -} - -/* Mobile Menu */ -.mobile-menu-button { - display: none !important; - background: none !important; - border: none !important; - font-size: 1.5rem !important; - color: #6b7280 !important; - cursor: pointer !important; -} - -/* Mobile Styles */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.5rem !important; - } - - .mobile-menu-button { - display: block !important; - } - - .navbar-nav { - display: none !important; - position: absolute !important; - top: 100% !important; - left: 0 !important; - right: 0 !important; - background: white !important; - flex-direction: column !important; - padding: 1rem !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; - } - - .navbar-nav.show { - display: flex !important; - } - - .section { - padding: 2rem 1rem !important; - } - - .section-title { - font-size: 2rem !important; - } -} - -/* Footer */ -.footer { - background: #1f2937 !important; - color: white !important; - padding: 3rem 2rem 1rem !important; - text-align: center !important; -} - -.footer p { - color: #d1d5db !important; -} - -/* Service Icons */ -.service-icon { - width: 80px !important; - height: 80px !important; - background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important; - border-radius: 50% !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - font-size: 2rem !important; - color: white !important; - margin: 0 auto 1rem !important; -} - -/* Portfolio Items */ -.portfolio-item { - background: white !important; - border-radius: 1rem !important; - overflow: hidden !important; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; - transition: all 0.3s ease !important; -} - -.portfolio-item:hover { - transform: translateY(-4px) !important; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important; -} - -.portfolio-image { - width: 100% !important; - height: 200px !important; - object-fit: cover !important; - display: block !important; -} - -.portfolio-content { - padding: 1.5rem !important; -} - -.portfolio-title { - font-size: 1.25rem !important; - font-weight: 600 !important; - margin-bottom: 0.5rem !important; - color: #1f2937 !important; -} - -.portfolio-description { - color: #6b7280 !important; - margin-bottom: 1rem !important; -} - -/* Form Styles */ -.form-group { - margin-bottom: 1.5rem !important; -} - -.form-label { - display: block !important; - font-weight: 500 !important; - margin-bottom: 0.5rem !important; - color: #374151 !important; -} - -.form-input, -.form-textarea, -.form-select { - width: 100% !important; - padding: 0.75rem !important; - border: 2px solid #e5e7eb !important; - border-radius: 0.5rem !important; - font-size: 1rem !important; - transition: all 0.3s ease !important; -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none !important; - border-color: #3B82F6 !important; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; -} \ No newline at end of file diff --git a/.history/public/css/custom_20251019164526.css b/.history/public/css/custom_20251019164526.css deleted file mode 100644 index cb2943d..0000000 --- a/.history/public/css/custom_20251019164526.css +++ /dev/null @@ -1,88 +0,0 @@ -/* 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; - } -} \ No newline at end of file diff --git a/.history/public/css/custom_20251019165556.css b/.history/public/css/custom_20251019165556.css deleted file mode 100644 index cb2943d..0000000 --- a/.history/public/css/custom_20251019165556.css +++ /dev/null @@ -1,88 +0,0 @@ -/* 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; - } -} \ No newline at end of file diff --git a/.history/public/css/dark-theme_20251019173803.css b/.history/public/css/dark-theme_20251019173803.css deleted file mode 100644 index 2d0cca4..0000000 --- a/.history/public/css/dark-theme_20251019173803.css +++ /dev/null @@ -1,315 +0,0 @@ -/* 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; - } -} \ No newline at end of file diff --git a/.history/public/css/dark-theme_20251019174052.css b/.history/public/css/dark-theme_20251019174052.css deleted file mode 100644 index 2d0cca4..0000000 --- a/.history/public/css/dark-theme_20251019174052.css +++ /dev/null @@ -1,315 +0,0 @@ -/* 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; - } -} \ No newline at end of file diff --git a/.history/public/css/dark-theme_20251020042230.css b/.history/public/css/dark-theme_20251020042230.css deleted file mode 100644 index 19d8f4f..0000000 --- a/.history/public/css/dark-theme_20251020042230.css +++ /dev/null @@ -1,315 +0,0 @@ -/* 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 { - color: white !important; - background-color: black !important; - } - - .dark .border { - border-width: 2px; - } -} - -/* Reduced motion for accessibility */ -@media (prefers-reduced-motion: reduce) { - .dark * { - transition: none !important; - animation: none !important; - } -} \ No newline at end of file diff --git a/.history/public/css/dark-theme_20251020042243.css b/.history/public/css/dark-theme_20251020042243.css deleted file mode 100644 index 19d8f4f..0000000 --- a/.history/public/css/dark-theme_20251020042243.css +++ /dev/null @@ -1,315 +0,0 @@ -/* 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 { - color: white !important; - background-color: black !important; - } - - .dark .border { - border-width: 2px; - } -} - -/* Reduced motion for accessibility */ -@media (prefers-reduced-motion: reduce) { - .dark * { - transition: none !important; - animation: none !important; - } -} \ No newline at end of file diff --git a/.history/public/css/fixes_20251019170837.css b/.history/public/css/fixes_20251019170837.css deleted file mode 100644 index 432d387..0000000 --- a/.history/public/css/fixes_20251019170837.css +++ /dev/null @@ -1,281 +0,0 @@ -/* 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,'); - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/fixes_20251019170927.css b/.history/public/css/fixes_20251019170927.css deleted file mode 100644 index 432d387..0000000 --- a/.history/public/css/fixes_20251019170927.css +++ /dev/null @@ -1,281 +0,0 @@ -/* 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,'); - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/fixes_20251021185301.css b/.history/public/css/fixes_20251021185301.css deleted file mode 100644 index d161529..0000000 --- a/.history/public/css/fixes_20251021185301.css +++ /dev/null @@ -1,310 +0,0 @@ -/* 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,'); - 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; - } -} - -/* iOS Style Theme Toggle */ -.theme-toggle-slider { - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -/* Theme toggle background states */ -input#theme-toggle:checked + label > div { - background-color: #4F46E5 !important; -} - -input#theme-toggle + label > div { - background-color: #D1D5DB; -} - -/* Dark mode adjustments for toggle */ -.dark input#theme-toggle + label > div { - background-color: #374151; -} - -.dark input#theme-toggle:checked + label > div { - background-color: #6366F1 !important; -} - -/* Smooth transitions for icons */ -.theme-sun-icon, -.theme-moon-icon { - transition: opacity 0.3s ease; -} - -/* Dark mode support (if needed) */ -@media (prefers-color-scheme: dark) { - .auto-dark { - color: #f9fafb; - background-color: #111827; - } -} \ No newline at end of file diff --git a/.history/public/css/fixes_20251021190951.css b/.history/public/css/fixes_20251021190951.css deleted file mode 100644 index d161529..0000000 --- a/.history/public/css/fixes_20251021190951.css +++ /dev/null @@ -1,310 +0,0 @@ -/* 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,'); - 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; - } -} - -/* iOS Style Theme Toggle */ -.theme-toggle-slider { - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -/* Theme toggle background states */ -input#theme-toggle:checked + label > div { - background-color: #4F46E5 !important; -} - -input#theme-toggle + label > div { - background-color: #D1D5DB; -} - -/* Dark mode adjustments for toggle */ -.dark input#theme-toggle + label > div { - background-color: #374151; -} - -.dark input#theme-toggle:checked + label > div { - background-color: #6366F1 !important; -} - -/* Smooth transitions for icons */ -.theme-sun-icon, -.theme-moon-icon { - transition: opacity 0.3s ease; -} - -/* Dark mode support (if needed) */ -@media (prefers-color-scheme: dark) { - .auto-dark { - color: #f9fafb; - background-color: #111827; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251019161505.css b/.history/public/css/main_20251019161505.css deleted file mode 100644 index 4ca83ed..0000000 --- a/.history/public/css/main_20251019161505.css +++ /dev/null @@ -1,425 +0,0 @@ -/* 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; -} \ No newline at end of file diff --git a/.history/public/css/main_20251019162545.css b/.history/public/css/main_20251019162545.css deleted file mode 100644 index 4ca83ed..0000000 --- a/.history/public/css/main_20251019162545.css +++ /dev/null @@ -1,425 +0,0 @@ -/* 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; -} \ No newline at end of file diff --git a/.history/public/css/main_20251019164257.css b/.history/public/css/main_20251019164257.css deleted file mode 100644 index 5e24d2d..0000000 --- a/.history/public/css/main_20251019164257.css +++ /dev/null @@ -1,427 +0,0 @@ -/* 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; -} \ No newline at end of file diff --git a/.history/public/css/main_20251019164506.css b/.history/public/css/main_20251019164506.css deleted file mode 100644 index 8f98e8c..0000000 --- a/.history/public/css/main_20251019164506.css +++ /dev/null @@ -1,442 +0,0 @@ -/* 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; -} \ No newline at end of file diff --git a/.history/public/css/main_20251019165556.css b/.history/public/css/main_20251019165556.css deleted file mode 100644 index 8f98e8c..0000000 --- a/.history/public/css/main_20251019165556.css +++ /dev/null @@ -1,442 +0,0 @@ -/* 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; -} \ No newline at end of file diff --git a/.history/public/css/main_20251019182154.css b/.history/public/css/main_20251019182154.css deleted file mode 100644 index e13ca7f..0000000 --- a/.history/public/css/main_20251019182154.css +++ /dev/null @@ -1,552 +0,0 @@ -/* 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251019182628.css b/.history/public/css/main_20251019182628.css deleted file mode 100644 index e13ca7f..0000000 --- a/.history/public/css/main_20251019182628.css +++ /dev/null @@ -1,552 +0,0 @@ -/* 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020042218.css b/.history/public/css/main_20251020042218.css deleted file mode 100644 index 815736f..0000000 --- a/.history/public/css/main_20251020042218.css +++ /dev/null @@ -1,554 +0,0 @@ -/* 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; -} - -.mobile-menu { - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020042243.css b/.history/public/css/main_20251020042243.css deleted file mode 100644 index 815736f..0000000 --- a/.history/public/css/main_20251020042243.css +++ /dev/null @@ -1,554 +0,0 @@ -/* 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; -} - -.mobile-menu { - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020045321.css b/.history/public/css/main_20251020045321.css deleted file mode 100644 index 4026b93..0000000 --- a/.history/public/css/main_20251020045321.css +++ /dev/null @@ -1,570 +0,0 @@ -/* SmartSolTech - Main Styles */ - -/* CSS Reset and Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - line-height: 1.6; - color: #1f2937; - background-color: #ffffff; -} - -/* Root Variables */ -:root { - --primary-color: #3B82F6; - --secondary-color: #8B5CF6; - --accent-color: #10B981; - --text-dark: #1f2937; - --text-light: #6b7280; - --bg-light: #f9fafb; - --border-color: #e5e7eb; -} - -/* 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; -} - -.mobile-menu { - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020045323.css b/.history/public/css/main_20251020045323.css deleted file mode 100644 index 4026b93..0000000 --- a/.history/public/css/main_20251020045323.css +++ /dev/null @@ -1,570 +0,0 @@ -/* SmartSolTech - Main Styles */ - -/* CSS Reset and Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - line-height: 1.6; - color: #1f2937; - background-color: #ffffff; -} - -/* Root Variables */ -:root { - --primary-color: #3B82F6; - --secondary-color: #8B5CF6; - --accent-color: #10B981; - --text-dark: #1f2937; - --text-light: #6b7280; - --bg-light: #f9fafb; - --border-color: #e5e7eb; -} - -/* 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; -} - -.mobile-menu { - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020045335.css b/.history/public/css/main_20251020045335.css deleted file mode 100644 index d45d96e..0000000 --- a/.history/public/css/main_20251020045335.css +++ /dev/null @@ -1,573 +0,0 @@ -/* SmartSolTech - Main Styles */ - -/* Tailwind Base (if not loading properly) */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - -/* CSS Reset and Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - line-height: 1.6; - color: #1f2937; - background-color: #ffffff; -} - -/* Root Variables */ -:root { - --primary-color: #3B82F6; - --secondary-color: #8B5CF6; - --accent-color: #10B981; - --text-dark: #1f2937; - --text-light: #6b7280; - --bg-light: #f9fafb; - --border-color: #e5e7eb; -} - -/* 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; -} - -.mobile-menu { - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020045359.css b/.history/public/css/main_20251020045359.css deleted file mode 100644 index d45d96e..0000000 --- a/.history/public/css/main_20251020045359.css +++ /dev/null @@ -1,573 +0,0 @@ -/* SmartSolTech - Main Styles */ - -/* Tailwind Base (if not loading properly) */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - -/* CSS Reset and Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - line-height: 1.6; - color: #1f2937; - background-color: #ffffff; -} - -/* Root Variables */ -:root { - --primary-color: #3B82F6; - --secondary-color: #8B5CF6; - --accent-color: #10B981; - --text-dark: #1f2937; - --text-light: #6b7280; - --bg-light: #f9fafb; - --border-color: #e5e7eb; -} - -/* 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; -} - -.mobile-menu { - 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; - } -} \ No newline at end of file diff --git a/.history/public/css/main_20251020225556.css b/.history/public/css/main_20251020225556.css deleted file mode 100644 index 8858763..0000000 --- a/.history/public/css/main_20251020225556.css +++ /dev/null @@ -1,606 +0,0 @@ -/* SmartSolTech - Main Styles */ - -/* Tailwind Base (if not loading properly) */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - -/* CSS Reset and Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - line-height: 1.6; - color: #1f2937; - background-color: #ffffff; -} - -/* Root Variables */ -:root { - --primary-color: #3B82F6; - --secondary-color: #8B5CF6; - --accent-color: #10B981; - --text-dark: #1f2937; - --text-light: #6b7280; - --bg-light: #f9fafb; - --border-color: #e5e7eb; -} - -/* 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; -} - -.mobile-menu { - 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: 2rem; - } -} - -/* Hero секции - компактные для внутренних страниц */ -.hero-section-compact { - min-height: 40vh !important; - max-height: 50vh !important; - padding: 4rem 0 !important; -} - -.hero-section-compact h1 { - font-size: 3rem !important; - margin-bottom: 1rem !important; -} - -.hero-section-compact p { - font-size: 1.125rem !important; - opacity: 0.9 !important; -} - -@media (max-width: 768px) { - .hero-section-compact { - min-height: 30vh !important; - padding: 3rem 0 !important; - } - - .hero-section-compact h1 { - font-size: 2.5rem !important; - } -} - -/* Полноэкранный Hero только для главной */ -.hero-section { - min-height: 100vh !important; -} \ No newline at end of file diff --git a/.history/public/css/main_20251020225722.css b/.history/public/css/main_20251020225722.css deleted file mode 100644 index 8858763..0000000 --- a/.history/public/css/main_20251020225722.css +++ /dev/null @@ -1,606 +0,0 @@ -/* SmartSolTech - Main Styles */ - -/* Tailwind Base (if not loading properly) */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - -/* CSS Reset and Base */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - line-height: 1.6; - color: #1f2937; - background-color: #ffffff; -} - -/* Root Variables */ -:root { - --primary-color: #3B82F6; - --secondary-color: #8B5CF6; - --accent-color: #10B981; - --text-dark: #1f2937; - --text-light: #6b7280; - --bg-light: #f9fafb; - --border-color: #e5e7eb; -} - -/* 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; -} - -.mobile-menu { - 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: 2rem; - } -} - -/* Hero секции - компактные для внутренних страниц */ -.hero-section-compact { - min-height: 40vh !important; - max-height: 50vh !important; - padding: 4rem 0 !important; -} - -.hero-section-compact h1 { - font-size: 3rem !important; - margin-bottom: 1rem !important; -} - -.hero-section-compact p { - font-size: 1.125rem !important; - opacity: 0.9 !important; -} - -@media (max-width: 768px) { - .hero-section-compact { - min-height: 30vh !important; - padding: 3rem 0 !important; - } - - .hero-section-compact h1 { - font-size: 2.5rem !important; - } -} - -/* Полноэкранный Hero только для главной */ -.hero-section { - min-height: 100vh !important; -} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026093454.css b/.history/public/css/sticky-price_20251026093454.css new file mode 100644 index 0000000..795310d --- /dev/null +++ b/.history/public/css/sticky-price_20251026093454.css @@ -0,0 +1,89 @@ +/* Sticky Price Display Styles */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); +} + +/* Enhanced visibility on scroll */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + transform: scale(1.02); +} + +/* Hover effects */ +#priceDisplay:hover { + transform: translateX(-5px) scale(1.05); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.3); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + top: auto !important; + right: auto !important; + margin: 1rem 0; + width: 100%; + } +} + +/* Dark mode enhancements */ +.dark #priceDisplay .bg-white { + background: rgba(31, 41, 55, 0.95); + backdrop-filter: blur(16px); +} + +/* Scroll-triggered animations */ +#priceDisplay.visible { + animation: slideInRight 0.5s ease-out; +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Enhanced shadow for better visibility */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.25), + 0 0 0 1px rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.5), + 0 0 0 1px rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026093548.css b/.history/public/css/sticky-price_20251026093548.css new file mode 100644 index 0000000..795310d --- /dev/null +++ b/.history/public/css/sticky-price_20251026093548.css @@ -0,0 +1,89 @@ +/* Sticky Price Display Styles */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); +} + +/* Enhanced visibility on scroll */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + transform: scale(1.02); +} + +/* Hover effects */ +#priceDisplay:hover { + transform: translateX(-5px) scale(1.05); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.3); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + top: auto !important; + right: auto !important; + margin: 1rem 0; + width: 100%; + } +} + +/* Dark mode enhancements */ +.dark #priceDisplay .bg-white { + background: rgba(31, 41, 55, 0.95); + backdrop-filter: blur(16px); +} + +/* Scroll-triggered animations */ +#priceDisplay.visible { + animation: slideInRight 0.5s ease-out; +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Enhanced shadow for better visibility */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.25), + 0 0 0 1px rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.5), + 0 0 0 1px rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095249.css b/.history/public/css/sticky-price_20251026095249.css new file mode 100644 index 0000000..01912cb --- /dev/null +++ b/.history/public/css/sticky-price_20251026095249.css @@ -0,0 +1,108 @@ +/* Dynamic Island Style Sticky Price Display */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(-50%); +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + transform: translateX(-50%) scale(1.02); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: translateX(-50%) scale(1.05); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.6); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateX(-50%) translateY(100%); + opacity: 0; + } + to { + transform: translateX(-50%) translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095257.css b/.history/public/css/sticky-price_20251026095257.css new file mode 100644 index 0000000..e44a2e7 --- /dev/null +++ b/.history/public/css/sticky-price_20251026095257.css @@ -0,0 +1,109 @@ +/* Dynamic Island Style Sticky Price Display */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(-50%); +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + transform: translateX(-50%) scale(1.02); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: translateX(-50%) scale(1.05); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.6); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateX(-50%) translateY(100%); + opacity: 0; + } + to { + transform: translateX(-50%) translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095324.css b/.history/public/css/sticky-price_20251026095324.css new file mode 100644 index 0000000..e44a2e7 --- /dev/null +++ b/.history/public/css/sticky-price_20251026095324.css @@ -0,0 +1,109 @@ +/* Dynamic Island Style Sticky Price Display */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(-50%); +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + transform: translateX(-50%) scale(1.02); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: translateX(-50%) scale(1.05); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.6); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateX(-50%) translateY(100%); + opacity: 0; + } + to { + transform: translateX(-50%) translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095604.css b/.history/public/css/sticky-price_20251026095604.css new file mode 100644 index 0000000..cf4da15 --- /dev/null +++ b/.history/public/css/sticky-price_20251026095604.css @@ -0,0 +1,114 @@ +/* Dynamic Island Style Sticky Price Display - Full Width */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); + left: 1rem; + right: 1rem; + max-width: calc(64rem - 2rem); /* max-w-4xl minus padding */ + margin-left: auto; + margin-right: auto; +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.4); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateX(-50%) translateY(100%); + opacity: 0; + } + to { + transform: translateX(-50%) translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095611.css b/.history/public/css/sticky-price_20251026095611.css new file mode 100644 index 0000000..82ee9ee --- /dev/null +++ b/.history/public/css/sticky-price_20251026095611.css @@ -0,0 +1,114 @@ +/* Dynamic Island Style Sticky Price Display - Full Width */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); + left: 1rem; + right: 1rem; + max-width: calc(64rem - 2rem); /* max-w-4xl minus padding */ + margin-left: auto; + margin-right: auto; +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.4); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095622.css b/.history/public/css/sticky-price_20251026095622.css new file mode 100644 index 0000000..3f74e16 --- /dev/null +++ b/.history/public/css/sticky-price_20251026095622.css @@ -0,0 +1,116 @@ +/* Dynamic Island Style Sticky Price Display - Full Width */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); + left: 1rem; + right: 1rem; + max-width: calc(64rem - 2rem); /* max-w-4xl minus padding */ + margin-left: auto; + margin-right: auto; +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.4); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + right: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + max-width: none; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026095647.css b/.history/public/css/sticky-price_20251026095647.css new file mode 100644 index 0000000..3f74e16 --- /dev/null +++ b/.history/public/css/sticky-price_20251026095647.css @@ -0,0 +1,116 @@ +/* Dynamic Island Style Sticky Price Display - Full Width */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); + left: 1rem; + right: 1rem; + max-width: calc(64rem - 2rem); /* max-w-4xl minus padding */ + margin-left: auto; + margin-right: auto; +} + +/* Enhanced visibility and Dynamic Island effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +/* Hover effects for Dynamic Island */ +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.4); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + position: relative !important; + bottom: auto !important; + left: auto !important; + right: auto !important; + transform: none !important; + margin: 1rem 0; + width: 100%; + max-width: none; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Dark mode enhancements for Dynamic Island */ +.dark #priceDisplay .bg-black\/80 { + background: rgba(17, 24, 39, 0.9); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); +} + +/* Dynamic Island entrance animation */ +#priceDisplay.visible { + animation: slideInBottom 0.5s ease-out; +} + +@keyframes slideInBottom { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced shadow for Dynamic Island aesthetic */ +#priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.dark #priceDisplay .shadow-2xl { + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026100151.css b/.history/public/css/sticky-price_20251026100151.css new file mode 100644 index 0000000..1ddb10e --- /dev/null +++ b/.history/public/css/sticky-price_20251026100151.css @@ -0,0 +1,69 @@ +/* Dynamic Price Island - As Calculator Extension */ +#priceIsland { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +#islandContainer { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +#islandContent { + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Hover effects for island */ +#priceIsland:hover #islandContainer { + transform: translateY(-2px); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); +} + +/* Animation for price updates */ +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceIsland { + margin: 1rem; + padding: 0; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026100440.css b/.history/public/css/sticky-price_20251026100440.css new file mode 100644 index 0000000..1ddb10e --- /dev/null +++ b/.history/public/css/sticky-price_20251026100440.css @@ -0,0 +1,69 @@ +/* Dynamic Price Island - As Calculator Extension */ +#priceIsland { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +#islandContainer { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +#islandContent { + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Hover effects for island */ +#priceIsland:hover #islandContainer { + transform: translateY(-2px); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); +} + +/* Animation for price updates */ +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceIsland { + margin: 1rem; + padding: 0; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026100707.css b/.history/public/css/sticky-price_20251026100707.css new file mode 100644 index 0000000..7253d33 --- /dev/null +++ b/.history/public/css/sticky-price_20251026100707.css @@ -0,0 +1,77 @@ +/* Price Display Island - Under Calculator */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Enhanced visibility and effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +/* Hover effects */ +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.4); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Animation for price updates */ +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + margin: 1rem; + padding: 0; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026100722.css b/.history/public/css/sticky-price_20251026100722.css new file mode 100644 index 0000000..7253d33 --- /dev/null +++ b/.history/public/css/sticky-price_20251026100722.css @@ -0,0 +1,77 @@ +/* Price Display Island - Under Calculator */ +#priceDisplay { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Enhanced visibility and effects */ +#priceDisplay.scrolled { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +/* Hover effects */ +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.4); +} + +/* Animation for price updates */ +#currentPrice { + transition: all 0.3s ease; +} + +#currentPrice.updating { + transform: scale(1.1); + filter: brightness(1.2); +} + +/* Animation for price updates */ +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Enhanced pulse animation for live indicator */ +.animate-pulse-soft { + animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-soft { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Mobile responsiveness improvements */ +@media (max-width: 1024px) { + #priceDisplay { + margin: 1rem; + padding: 0; + } + + #mobilePriceDisplay { + transform: none; + } + + #mobilePriceDisplay:hover { + transform: scale(1.02); + } +} + +/* Mobile Dynamic Island styling */ +#mobilePriceDisplay .shadow-2xl { + box-shadow: + 0 20px 40px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026100840.css b/.history/public/css/sticky-price_20251026100840.css new file mode 100644 index 0000000..02587bb --- /dev/null +++ b/.history/public/css/sticky-price_20251026100840.css @@ -0,0 +1,46 @@ +/* Simple Sticky Price Display */ +#priceDisplay { + /* Positioned under calculator with 10px gap */ + margin-top: 10px; + transition: all 0.3s ease; + + /* Ensure proper display when visible */ + opacity: 1; + transform: translateY(0); +} + +/* Hidden state */ +#priceDisplay.hidden { + opacity: 0; + transform: translateY(-10px); + pointer-events: none; +} + +/* Smooth show animation */ +#priceDisplay.show { + opacity: 1; + transform: translateY(0); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Mobile responsiveness */ +@media (max-width: 768px) { + #priceDisplay { + margin-top: 8px; + padding: 12px 16px; + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026100852.css b/.history/public/css/sticky-price_20251026100852.css new file mode 100644 index 0000000..02587bb --- /dev/null +++ b/.history/public/css/sticky-price_20251026100852.css @@ -0,0 +1,46 @@ +/* Simple Sticky Price Display */ +#priceDisplay { + /* Positioned under calculator with 10px gap */ + margin-top: 10px; + transition: all 0.3s ease; + + /* Ensure proper display when visible */ + opacity: 1; + transform: translateY(0); +} + +/* Hidden state */ +#priceDisplay.hidden { + opacity: 0; + transform: translateY(-10px); + pointer-events: none; +} + +/* Smooth show animation */ +#priceDisplay.show { + opacity: 1; + transform: translateY(0); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Mobile responsiveness */ +@media (max-width: 768px) { + #priceDisplay { + margin-top: 8px; + padding: 12px 16px; + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026102108.css b/.history/public/css/sticky-price_20251026102108.css new file mode 100644 index 0000000..df22028 --- /dev/null +++ b/.history/public/css/sticky-price_20251026102108.css @@ -0,0 +1,91 @@ +/* Sticky Price Display with backdrop blur */ +#stickyPriceContainer { + /* Enhanced backdrop blur effect */ + backdrop-filter: blur(12px) saturate(180%); + -webkit-backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation entrance */ + animation: slideUpFade 0.5s ease-out; +} + +#stickyPriceContainer.show { + transform: translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateY(100%); + opacity: 0; +} + +/* Price display inner styling */ +#priceDisplay { + transition: all 0.2s ease; +} + +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Slide up animation */ +@keyframes slideUpFade { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced mobile responsiveness */ +@media (max-width: 768px) { + #stickyPriceContainer { + padding: 0; + } + + #stickyPriceContainer .mx-auto { + margin-left: 1rem; + margin-right: 1rem; + } + + #priceDisplay { + padding: 12px 16px; + border-radius: 12px; + } + + #currentPrice { + font-size: 1.125rem; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + #stickyPriceContainer { + background: rgba(255, 255, 255, 0.9); + } + + .dark #stickyPriceContainer { + background: rgba(17, 24, 39, 0.9); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026102116.css b/.history/public/css/sticky-price_20251026102116.css new file mode 100644 index 0000000..10b36ba --- /dev/null +++ b/.history/public/css/sticky-price_20251026102116.css @@ -0,0 +1,91 @@ +/* Sticky Price Display with backdrop blur */ +#stickyPriceContainer { + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation entrance */ + animation: slideUpFade 0.5s ease-out; +} + +#stickyPriceContainer.show { + transform: translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateY(100%); + opacity: 0; +} + +/* Price display inner styling */ +#priceDisplay { + transition: all 0.2s ease; +} + +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Slide up animation */ +@keyframes slideUpFade { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced mobile responsiveness */ +@media (max-width: 768px) { + #stickyPriceContainer { + padding: 0; + } + + #stickyPriceContainer .mx-auto { + margin-left: 1rem; + margin-right: 1rem; + } + + #priceDisplay { + padding: 12px 16px; + border-radius: 12px; + } + + #currentPrice { + font-size: 1.125rem; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + #stickyPriceContainer { + background: rgba(255, 255, 255, 0.9); + } + + .dark #stickyPriceContainer { + background: rgba(17, 24, 39, 0.9); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026102247.css b/.history/public/css/sticky-price_20251026102247.css new file mode 100644 index 0000000..10b36ba --- /dev/null +++ b/.history/public/css/sticky-price_20251026102247.css @@ -0,0 +1,91 @@ +/* Sticky Price Display with backdrop blur */ +#stickyPriceContainer { + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation entrance */ + animation: slideUpFade 0.5s ease-out; +} + +#stickyPriceContainer.show { + transform: translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateY(100%); + opacity: 0; +} + +/* Price display inner styling */ +#priceDisplay { + transition: all 0.2s ease; +} + +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Slide up animation */ +@keyframes slideUpFade { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced mobile responsiveness */ +@media (max-width: 768px) { + #stickyPriceContainer { + padding: 0; + } + + #stickyPriceContainer .mx-auto { + margin-left: 1rem; + margin-right: 1rem; + } + + #priceDisplay { + padding: 12px 16px; + border-radius: 12px; + } + + #currentPrice { + font-size: 1.125rem; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + #stickyPriceContainer { + background: rgba(255, 255, 255, 0.9); + } + + .dark #stickyPriceContainer { + background: rgba(17, 24, 39, 0.9); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026102653.css b/.history/public/css/sticky-price_20251026102653.css new file mode 100644 index 0000000..8264cd3 --- /dev/null +++ b/.history/public/css/sticky-price_20251026102653.css @@ -0,0 +1,103 @@ +/* Sticky Price Display with backdrop blur */ +#stickyPriceContainer { + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation entrance */ + animation: slideUpFade 0.5s ease-out; +} + +/* Ensure exact width alignment with calculator */ +#stickyPriceContainer .container { + /* Match exactly the main calculator container */ + max-width: 56rem; /* max-w-4xl = 896px */ +} + +#priceDisplay { + /* Remove extra padding to match calculator content width */ + margin-left: 0; + margin-right: 0; +} + +#stickyPriceContainer.show { + transform: translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateY(100%); + opacity: 0; +} + +/* Price display inner styling */ +#priceDisplay { + transition: all 0.2s ease; +} + +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Slide up animation */ +@keyframes slideUpFade { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced mobile responsiveness */ +@media (max-width: 768px) { + #stickyPriceContainer { + padding: 0; + } + + #stickyPriceContainer .mx-auto { + margin-left: 1rem; + margin-right: 1rem; + } + + #priceDisplay { + padding: 12px 16px; + border-radius: 12px; + } + + #currentPrice { + font-size: 1.125rem; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + #stickyPriceContainer { + background: rgba(255, 255, 255, 0.9); + } + + .dark #stickyPriceContainer { + background: rgba(17, 24, 39, 0.9); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026102704.css b/.history/public/css/sticky-price_20251026102704.css new file mode 100644 index 0000000..4dfc249 --- /dev/null +++ b/.history/public/css/sticky-price_20251026102704.css @@ -0,0 +1,109 @@ +/* Sticky Price Display with backdrop blur */ +#stickyPriceContainer { + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation entrance */ + animation: slideUpFade 0.5s ease-out; +} + +/* Ensure exact width alignment with calculator */ +#stickyPriceContainer .container { + /* Match exactly the main calculator container */ + max-width: 56rem; /* max-w-4xl = 896px */ +} + +#priceDisplay { + /* Remove extra padding to match calculator content width */ + margin-left: 0; + margin-right: 0; +} + +/* Header and Calculator alignment */ +.calculator-width-alignment { + /* Ensure all elements use exactly same content width */ + max-width: 56rem; /* max-w-4xl */ +} + +#stickyPriceContainer.show { + transform: translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateY(100%); + opacity: 0; +} + +/* Price display inner styling */ +#priceDisplay { + transition: all 0.2s ease; +} + +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Slide up animation */ +@keyframes slideUpFade { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced mobile responsiveness */ +@media (max-width: 768px) { + #stickyPriceContainer { + padding: 0; + } + + #stickyPriceContainer .mx-auto { + margin-left: 1rem; + margin-right: 1rem; + } + + #priceDisplay { + padding: 12px 16px; + border-radius: 12px; + } + + #currentPrice { + font-size: 1.125rem; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + #stickyPriceContainer { + background: rgba(255, 255, 255, 0.9); + } + + .dark #stickyPriceContainer { + background: rgba(17, 24, 39, 0.9); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026102723.css b/.history/public/css/sticky-price_20251026102723.css new file mode 100644 index 0000000..4dfc249 --- /dev/null +++ b/.history/public/css/sticky-price_20251026102723.css @@ -0,0 +1,109 @@ +/* Sticky Price Display with backdrop blur */ +#stickyPriceContainer { + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation entrance */ + animation: slideUpFade 0.5s ease-out; +} + +/* Ensure exact width alignment with calculator */ +#stickyPriceContainer .container { + /* Match exactly the main calculator container */ + max-width: 56rem; /* max-w-4xl = 896px */ +} + +#priceDisplay { + /* Remove extra padding to match calculator content width */ + margin-left: 0; + margin-right: 0; +} + +/* Header and Calculator alignment */ +.calculator-width-alignment { + /* Ensure all elements use exactly same content width */ + max-width: 56rem; /* max-w-4xl */ +} + +#stickyPriceContainer.show { + transform: translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateY(100%); + opacity: 0; +} + +/* Price display inner styling */ +#priceDisplay { + transition: all 0.2s ease; +} + +#priceDisplay:hover { + transform: scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +/* Price update animation */ +#currentPrice { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Slide up animation */ +@keyframes slideUpFade { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Enhanced mobile responsiveness */ +@media (max-width: 768px) { + #stickyPriceContainer { + padding: 0; + } + + #stickyPriceContainer .mx-auto { + margin-left: 1rem; + margin-right: 1rem; + } + + #priceDisplay { + padding: 12px 16px; + border-radius: 12px; + } + + #currentPrice { + font-size: 1.125rem; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + #stickyPriceContainer { + background: rgba(255, 255, 255, 0.9); + } + + .dark #stickyPriceContainer { + background: rgba(17, 24, 39, 0.9); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026103959.css b/.history/public/css/sticky-price_20251026103959.css new file mode 100644 index 0000000..be2abe1 --- /dev/null +++ b/.history/public/css/sticky-price_20251026103959.css @@ -0,0 +1,195 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 400px; + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: 350px; + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026104007.css b/.history/public/css/sticky-price_20251026104007.css new file mode 100644 index 0000000..da2f909 --- /dev/null +++ b/.history/public/css/sticky-price_20251026104007.css @@ -0,0 +1,196 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 400px; + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: 350px; + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026104239.css b/.history/public/css/sticky-price_20251026104239.css new file mode 100644 index 0000000..da2f909 --- /dev/null +++ b/.history/public/css/sticky-price_20251026104239.css @@ -0,0 +1,196 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 400px; + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: 350px; + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026104350.css b/.history/public/css/sticky-price_20251026104350.css new file mode 100644 index 0000000..040110a --- /dev/null +++ b/.history/public/css/sticky-price_20251026104350.css @@ -0,0 +1,196 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 56rem; /* max-w-4xl = 896px - match calculator width */ + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: 350px; + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026104400.css b/.history/public/css/sticky-price_20251026104400.css new file mode 100644 index 0000000..d6ea963 --- /dev/null +++ b/.history/public/css/sticky-price_20251026104400.css @@ -0,0 +1,197 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 56rem; /* max-w-4xl = 896px - match calculator width */ + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: calc(100% - 2rem); /* Full width minus padding on mobile */ + width: calc(100% - 2rem); + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/.history/public/css/sticky-price_20251026104409.css b/.history/public/css/sticky-price_20251026104409.css new file mode 100644 index 0000000..d6ea963 --- /dev/null +++ b/.history/public/css/sticky-price_20251026104409.css @@ -0,0 +1,197 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 56rem; /* max-w-4xl = 896px - match calculator width */ + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: calc(100% - 2rem); /* Full width minus padding on mobile */ + width: calc(100% - 2rem); + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092259.css b/.history/public/css/theme-toggle_20251026092259.css new file mode 100644 index 0000000..ed339af --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092259.css @@ -0,0 +1,87 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Enhanced toggle background gradient */ +.theme-toggle-bg { + background: linear-gradient(45deg, #dbeafe, #fef3c7); + transition: background 0.3s ease; +} + +.dark .theme-toggle-bg { + background: linear-gradient(45deg, #374151, #4b5563); +} + +/* Hover effects */ +.theme-toggle-slider:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label .theme-toggle-slider { + ring: 2px; + ring-color: #3b82f6; + ring-offset: 2px; +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092307.css b/.history/public/css/theme-toggle_20251026092307.css new file mode 100644 index 0000000..44ab4e4 --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092307.css @@ -0,0 +1,85 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Enhanced toggle background gradient */ +.theme-toggle-bg { + background: linear-gradient(45deg, #dbeafe, #fef3c7); + transition: background 0.3s ease; +} + +.dark .theme-toggle-bg { + background: linear-gradient(45deg, #374151, #4b5563); +} + +/* Hover effects */ +.theme-toggle-slider:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label .theme-toggle-slider { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092353.css b/.history/public/css/theme-toggle_20251026092353.css new file mode 100644 index 0000000..44ab4e4 --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092353.css @@ -0,0 +1,85 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Enhanced toggle background gradient */ +.theme-toggle-bg { + background: linear-gradient(45deg, #dbeafe, #fef3c7); + transition: background 0.3s ease; +} + +.dark .theme-toggle-bg { + background: linear-gradient(45deg, #374151, #4b5563); +} + +/* Hover effects */ +.theme-toggle-slider:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label .theme-toggle-slider { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092555.css b/.history/public/css/theme-toggle_20251026092555.css new file mode 100644 index 0000000..ca00042 --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092555.css @@ -0,0 +1,99 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092650.css b/.history/public/css/theme-toggle_20251026092650.css new file mode 100644 index 0000000..ca00042 --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092650.css @@ -0,0 +1,99 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092927.css b/.history/public/css/theme-toggle_20251026092927.css new file mode 100644 index 0000000..f37101a --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092927.css @@ -0,0 +1,107 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement with perfect centering */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; + position: relative; + display: flex; + align-items: center; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles with perfect centering */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + /* Убираем top и left позиционирование для лучшего центрирования */ + position: absolute; + top: 50%; + margin-top: -10px; /* Половина высоты ползунка (20px / 2) */ + left: 4px; /* Отступ от края */ +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026092941.css b/.history/public/css/theme-toggle_20251026092941.css new file mode 100644 index 0000000..f37101a --- /dev/null +++ b/.history/public/css/theme-toggle_20251026092941.css @@ -0,0 +1,107 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement with perfect centering */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; + position: relative; + display: flex; + align-items: center; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles with perfect centering */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + /* Убираем top и left позиционирование для лучшего центрирования */ + position: absolute; + top: 50%; + margin-top: -10px; /* Половина высоты ползунка (20px / 2) */ + left: 4px; /* Отступ от края */ +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026093055.css b/.history/public/css/theme-toggle_20251026093055.css new file mode 100644 index 0000000..57b66eb --- /dev/null +++ b/.history/public/css/theme-toggle_20251026093055.css @@ -0,0 +1,106 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement with perfect centering */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; + position: relative; + display: flex; + align-items: center; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles with perfect centering */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + /* Более точное центрирование */ + position: absolute; + top: 2px; /* Фиксированная позиция сверху для лучшего контроля */ + left: 4px; /* Отступ от края */ +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026093120.css b/.history/public/css/theme-toggle_20251026093120.css new file mode 100644 index 0000000..77d6edd --- /dev/null +++ b/.history/public/css/theme-toggle_20251026093120.css @@ -0,0 +1,103 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement with perfect centering */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; + position: relative; + display: flex; + align-items: center; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles with perfect centering */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + /* Позволяем Tailwind CSS управлять позиционированием */ +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/css/theme-toggle_20251026093149.css b/.history/public/css/theme-toggle_20251026093149.css new file mode 100644 index 0000000..77d6edd --- /dev/null +++ b/.history/public/css/theme-toggle_20251026093149.css @@ -0,0 +1,103 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement with perfect centering */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; + position: relative; + display: flex; + align-items: center; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles with perfect centering */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + /* Позволяем Tailwind CSS управлять позиционированием */ +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/.history/public/images/icon-144x144_20251020042420.png b/.history/public/images/icon-144x144_20251020042420.png deleted file mode 100644 index 6edd642..0000000 --- a/.history/public/images/icon-144x144_20251020042420.png +++ /dev/null @@ -1,4 +0,0 @@ - - - ST - \ No newline at end of file diff --git a/.history/public/images/icon-144x144_20251020042429.png b/.history/public/images/icon-144x144_20251020042429.png deleted file mode 100644 index 6edd642..0000000 --- a/.history/public/images/icon-144x144_20251020042429.png +++ /dev/null @@ -1,4 +0,0 @@ - - - ST - \ No newline at end of file diff --git a/.history/public/images/icons/icon-192x192_20251020041929.png b/.history/public/images/icons/icon-192x192_20251020041929.png deleted file mode 100644 index 6d2ce2b..0000000 --- a/.history/public/images/icons/icon-192x192_20251020041929.png +++ /dev/null @@ -1,4 +0,0 @@ - - - ST - \ No newline at end of file diff --git a/.history/public/images/icons/icon-192x192_20251020041934.png b/.history/public/images/icons/icon-192x192_20251020041934.png deleted file mode 100644 index 6d2ce2b..0000000 --- a/.history/public/images/icons/icon-192x192_20251020041934.png +++ /dev/null @@ -1,4 +0,0 @@ - - - ST - \ No newline at end of file diff --git a/.history/public/images/logo_20251020041922.png b/.history/public/images/logo_20251020041922.png deleted file mode 100644 index 4054628..0000000 --- a/.history/public/images/logo_20251020041922.png +++ /dev/null @@ -1,4 +0,0 @@ - - - ST - \ No newline at end of file diff --git a/.history/public/images/logo_20251020041933.png b/.history/public/images/logo_20251020041933.png deleted file mode 100644 index 4054628..0000000 --- a/.history/public/images/logo_20251020041933.png +++ /dev/null @@ -1,4 +0,0 @@ - - - ST - \ No newline at end of file diff --git a/.history/public/images/portfolio/corporate-1_20251020041915.jpg b/.history/public/images/portfolio/corporate-1_20251020041915.jpg deleted file mode 100644 index 5649601..0000000 --- a/.history/public/images/portfolio/corporate-1_20251020041915.jpg +++ /dev/null @@ -1,4 +0,0 @@ - - - Corporate Website - \ No newline at end of file diff --git a/.history/public/images/portfolio/corporate-1_20251020041933.jpg b/.history/public/images/portfolio/corporate-1_20251020041933.jpg deleted file mode 100644 index 5649601..0000000 --- a/.history/public/images/portfolio/corporate-1_20251020041933.jpg +++ /dev/null @@ -1,4 +0,0 @@ - - - Corporate Website - \ No newline at end of file diff --git a/.history/public/images/portfolio/ecommerce-1_20251020041900.jpg b/.history/public/images/portfolio/ecommerce-1_20251020041900.jpg deleted file mode 100644 index 559feb7..0000000 --- a/.history/public/images/portfolio/ecommerce-1_20251020041900.jpg +++ /dev/null @@ -1,4 +0,0 @@ - - - E-commerce Project - \ No newline at end of file diff --git a/.history/public/images/portfolio/ecommerce-1_20251020041933.jpg b/.history/public/images/portfolio/ecommerce-1_20251020041933.jpg deleted file mode 100644 index 559feb7..0000000 --- a/.history/public/images/portfolio/ecommerce-1_20251020041933.jpg +++ /dev/null @@ -1,4 +0,0 @@ - - - E-commerce Project - \ No newline at end of file diff --git a/.history/public/images/portfolio/fitness-1_20251020041907.jpg b/.history/public/images/portfolio/fitness-1_20251020041907.jpg deleted file mode 100644 index 70126d4..0000000 --- a/.history/public/images/portfolio/fitness-1_20251020041907.jpg +++ /dev/null @@ -1,4 +0,0 @@ - - - Fitness App Project - \ No newline at end of file diff --git a/.history/public/images/portfolio/fitness-1_20251020041933.jpg b/.history/public/images/portfolio/fitness-1_20251020041933.jpg deleted file mode 100644 index 70126d4..0000000 --- a/.history/public/images/portfolio/fitness-1_20251020041933.jpg +++ /dev/null @@ -1,4 +0,0 @@ - - - Fitness App Project - \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026100125.js b/.history/public/js/calculator-modern_20251026100125.js new file mode 100644 index 0000000..4b1a016 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026100125.js @@ -0,0 +1,641 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (this.state.selectedService) { + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.updatePriceIsland(); // Add integration with price island + this.saveToStorage(); + } + + // New method to update price island + updatePriceIsland() { + if (window.priceIsland) { + const selectedServices = []; + + // Add selected service + if (this.state.selectedService) { + const service = this.services[this.state.selectedService]; + selectedServices.push({ + name: service.name, + price: service.basePrice + }); + } + + // Prepare data for island + const islandData = { + selectedServices: selectedServices, + complexity: this.state.selectedComplexity ? { + name: this.complexity[this.state.selectedComplexity].name, + multiplier: this.complexity[this.state.selectedComplexity].multiplier + } : null, + timeline: this.state.selectedTimeline ? { + name: this.timeline[this.state.selectedTimeline].name, + multiplier: this.timeline[this.state.selectedTimeline].multiplier + } : null, + totalPrice: this.calculatePrice() + }; + + // Dispatch event to update island + document.dispatchEvent(new CustomEvent('calculatorStateChange', { + detail: islandData + })); + } + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026100440.js b/.history/public/js/calculator-modern_20251026100440.js new file mode 100644 index 0000000..4b1a016 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026100440.js @@ -0,0 +1,641 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (this.state.selectedService) { + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.updatePriceIsland(); // Add integration with price island + this.saveToStorage(); + } + + // New method to update price island + updatePriceIsland() { + if (window.priceIsland) { + const selectedServices = []; + + // Add selected service + if (this.state.selectedService) { + const service = this.services[this.state.selectedService]; + selectedServices.push({ + name: service.name, + price: service.basePrice + }); + } + + // Prepare data for island + const islandData = { + selectedServices: selectedServices, + complexity: this.state.selectedComplexity ? { + name: this.complexity[this.state.selectedComplexity].name, + multiplier: this.complexity[this.state.selectedComplexity].multiplier + } : null, + timeline: this.state.selectedTimeline ? { + name: this.timeline[this.state.selectedTimeline].name, + multiplier: this.timeline[this.state.selectedTimeline].multiplier + } : null, + totalPrice: this.calculatePrice() + }; + + // Dispatch event to update island + document.dispatchEvent(new CustomEvent('calculatorStateChange', { + detail: islandData + })); + } + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026100647.js b/.history/public/js/calculator-modern_20251026100647.js new file mode 100644 index 0000000..ad19423 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026100647.js @@ -0,0 +1,605 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (this.state.selectedService) { + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026100722.js b/.history/public/js/calculator-modern_20251026100722.js new file mode 100644 index 0000000..ad19423 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026100722.js @@ -0,0 +1,605 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (this.state.selectedService) { + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101525.js b/.history/public/js/calculator-modern_20251026101525.js new file mode 100644 index 0000000..b6edff8 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101525.js @@ -0,0 +1,606 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + // Show price display if service is selected OR price > 0 + if (this.state.selectedService || price > 0) { + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101528.js b/.history/public/js/calculator-modern_20251026101528.js new file mode 100644 index 0000000..b6edff8 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101528.js @@ -0,0 +1,606 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + // Show price display if service is selected OR price > 0 + if (this.state.selectedService || price > 0) { + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101548.js b/.history/public/js/calculator-modern_20251026101548.js new file mode 100644 index 0000000..c77eab8 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101548.js @@ -0,0 +1,616 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + console.log('updatePriceDisplay called:', { + price, + formattedPrice, + selectedService: this.state.selectedService + }); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + console.log('Price display elements:', { priceDisplay, mobilePriceDisplay }); + + // Show price display if service is selected OR price > 0 + if (this.state.selectedService || price > 0) { + console.log('Showing price display'); + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + console.log('Hiding price display'); + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101557.js b/.history/public/js/calculator-modern_20251026101557.js new file mode 100644 index 0000000..acebc4b --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101557.js @@ -0,0 +1,629 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + console.log('Calculator initializing...'); + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Force show price display for testing + setTimeout(() => { + console.log('Force showing price display...'); + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + console.log('Price display shown'); + } else { + console.log('Price display not found!'); + } + }, 1000); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + console.log('updatePriceDisplay called:', { + price, + formattedPrice, + selectedService: this.state.selectedService + }); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + console.log('Price display elements:', { priceDisplay, mobilePriceDisplay }); + + // Show price display if service is selected OR price > 0 + if (this.state.selectedService || price > 0) { + console.log('Showing price display'); + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + console.log('Hiding price display'); + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101633.js b/.history/public/js/calculator-modern_20251026101633.js new file mode 100644 index 0000000..2431ea3 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101633.js @@ -0,0 +1,624 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Ensure price display is visible on page load + setTimeout(() => { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + console.log('updatePriceDisplay called:', { + price, + formattedPrice, + selectedService: this.state.selectedService + }); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show/hide price displays + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + console.log('Price display elements:', { priceDisplay, mobilePriceDisplay }); + + // Show price display if service is selected OR price > 0 + if (this.state.selectedService || price > 0) { + console.log('Showing price display'); + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } else { + console.log('Hiding price display'); + if (priceDisplay) priceDisplay.classList.add('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.add('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101646.js b/.history/public/js/calculator-modern_20251026101646.js new file mode 100644 index 0000000..df9b8ba --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101646.js @@ -0,0 +1,608 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Ensure price display is visible on page load + setTimeout(() => { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Always show price display once initialized + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026101730.js b/.history/public/js/calculator-modern_20251026101730.js new file mode 100644 index 0000000..df9b8ba --- /dev/null +++ b/.history/public/js/calculator-modern_20251026101730.js @@ -0,0 +1,608 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Ensure price display is visible on page load + setTimeout(() => { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Always show price display once initialized + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (priceDisplay) priceDisplay.classList.remove('hidden'); + if (mobilePriceDisplay) mobilePriceDisplay.classList.remove('hidden'); + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026102039.js b/.history/public/js/calculator-modern_20251026102039.js new file mode 100644 index 0000000..a43465d --- /dev/null +++ b/.history/public/js/calculator-modern_20251026102039.js @@ -0,0 +1,612 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Ensure price display is visible on page load + setTimeout(() => { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + if (mobilePriceDisplay) { + mobilePriceDisplay.classList.remove('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026102048.js b/.history/public/js/calculator-modern_20251026102048.js new file mode 100644 index 0000000..d424900 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026102048.js @@ -0,0 +1,612 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Show sticky price display on page load + setTimeout(() => { + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays + const priceElements = ['currentPrice', 'mobilePriceValue', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + if (mobilePriceDisplay) { + mobilePriceDisplay.classList.remove('hidden'); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026102220.js b/.history/public/js/calculator-modern_20251026102220.js new file mode 100644 index 0000000..2f546bf --- /dev/null +++ b/.history/public/js/calculator-modern_20251026102220.js @@ -0,0 +1,608 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Show sticky price display on page load + setTimeout(() => { + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays (remove mobilePriceValue since we removed mobile block) + const priceElements = ['currentPrice', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026102247.js b/.history/public/js/calculator-modern_20251026102247.js new file mode 100644 index 0000000..2f546bf --- /dev/null +++ b/.history/public/js/calculator-modern_20251026102247.js @@ -0,0 +1,608 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Show sticky price display on page load + setTimeout(() => { + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update all price displays (remove mobilePriceValue since we removed mobile block) + const priceElements = ['currentPrice', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026103724.js b/.history/public/js/calculator-modern_20251026103724.js new file mode 100644 index 0000000..dee3df8 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026103724.js @@ -0,0 +1,676 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Show sticky price display on page load + setTimeout(() => { + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update main price displays + const priceElements = ['currentPrice', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Update detailed breakdown + this.updatePriceBreakdown(); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + } + + updatePriceBreakdown() { + // Service selection + const selectedServiceDiv = document.getElementById('selectedService'); + const serviceName = document.getElementById('serviceName'); + const servicePrice = document.getElementById('servicePrice'); + + if (this.state.selectedService && selectedServiceDiv && serviceName && servicePrice) { + const service = this.services[this.state.selectedService]; + serviceName.textContent = service.name; + servicePrice.textContent = this.formatPrice(service.basePrice); + selectedServiceDiv.classList.remove('hidden'); + } else if (selectedServiceDiv) { + selectedServiceDiv.classList.add('hidden'); + } + + // Complexity + const selectedComplexityDiv = document.getElementById('selectedComplexity'); + const complexityName = document.getElementById('complexityName'); + const complexityMultiplier = document.getElementById('complexityMultiplier'); + + if (this.state.selectedComplexity && selectedComplexityDiv && complexityName && complexityMultiplier) { + const complexity = this.complexity[this.state.selectedComplexity]; + complexityName.textContent = complexity.name; + complexityMultiplier.textContent = complexity.multiplier; + selectedComplexityDiv.classList.remove('hidden'); + } else if (selectedComplexityDiv) { + selectedComplexityDiv.classList.add('hidden'); + } + + // Timeline + const selectedTimelineDiv = document.getElementById('selectedTimeline'); + const timelineName = document.getElementById('timelineName'); + const timelineMultiplier = document.getElementById('timelineMultiplier'); + + if (this.state.selectedTimeline && selectedTimelineDiv && timelineName && timelineMultiplier) { + const timeline = this.timeline[this.state.selectedTimeline]; + timelineName.textContent = timeline.name; + timelineMultiplier.textContent = timeline.multiplier; + selectedTimelineDiv.classList.remove('hidden'); + } else if (selectedTimelineDiv) { + selectedTimelineDiv.classList.add('hidden'); + } + + // Promo code + const appliedPromoDiv = document.getElementById('appliedPromo'); + const promoCode = document.getElementById('promoCode'); + const promoDiscount = document.getElementById('promoDiscount'); + + if (this.state.promoCode && this.promoCodes[this.state.promoCode.toUpperCase()] && appliedPromoDiv && promoCode && promoDiscount) { + const discount = this.promoCodes[this.state.promoCode.toUpperCase()]; + const discountPercent = Math.round((1 - discount) * 100); + promoCode.textContent = this.state.promoCode.toUpperCase(); + promoDiscount.textContent = `-${discountPercent}%`; + appliedPromoDiv.classList.remove('hidden'); + } else if (appliedPromoDiv) { + appliedPromoDiv.classList.add('hidden'); + } + + // Final calculation + const finalCalculation = document.getElementById('finalCalculation'); + if (finalCalculation) { + const price = this.calculatePrice(); + finalCalculation.textContent = this.formatPrice(price); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator-modern_20251026104239.js b/.history/public/js/calculator-modern_20251026104239.js new file mode 100644 index 0000000..dee3df8 --- /dev/null +++ b/.history/public/js/calculator-modern_20251026104239.js @@ -0,0 +1,676 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Show sticky price display on page load + setTimeout(() => { + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update main price displays + const priceElements = ['currentPrice', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Update detailed breakdown + this.updatePriceBreakdown(); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + } + + updatePriceBreakdown() { + // Service selection + const selectedServiceDiv = document.getElementById('selectedService'); + const serviceName = document.getElementById('serviceName'); + const servicePrice = document.getElementById('servicePrice'); + + if (this.state.selectedService && selectedServiceDiv && serviceName && servicePrice) { + const service = this.services[this.state.selectedService]; + serviceName.textContent = service.name; + servicePrice.textContent = this.formatPrice(service.basePrice); + selectedServiceDiv.classList.remove('hidden'); + } else if (selectedServiceDiv) { + selectedServiceDiv.classList.add('hidden'); + } + + // Complexity + const selectedComplexityDiv = document.getElementById('selectedComplexity'); + const complexityName = document.getElementById('complexityName'); + const complexityMultiplier = document.getElementById('complexityMultiplier'); + + if (this.state.selectedComplexity && selectedComplexityDiv && complexityName && complexityMultiplier) { + const complexity = this.complexity[this.state.selectedComplexity]; + complexityName.textContent = complexity.name; + complexityMultiplier.textContent = complexity.multiplier; + selectedComplexityDiv.classList.remove('hidden'); + } else if (selectedComplexityDiv) { + selectedComplexityDiv.classList.add('hidden'); + } + + // Timeline + const selectedTimelineDiv = document.getElementById('selectedTimeline'); + const timelineName = document.getElementById('timelineName'); + const timelineMultiplier = document.getElementById('timelineMultiplier'); + + if (this.state.selectedTimeline && selectedTimelineDiv && timelineName && timelineMultiplier) { + const timeline = this.timeline[this.state.selectedTimeline]; + timelineName.textContent = timeline.name; + timelineMultiplier.textContent = timeline.multiplier; + selectedTimelineDiv.classList.remove('hidden'); + } else if (selectedTimelineDiv) { + selectedTimelineDiv.classList.add('hidden'); + } + + // Promo code + const appliedPromoDiv = document.getElementById('appliedPromo'); + const promoCode = document.getElementById('promoCode'); + const promoDiscount = document.getElementById('promoDiscount'); + + if (this.state.promoCode && this.promoCodes[this.state.promoCode.toUpperCase()] && appliedPromoDiv && promoCode && promoDiscount) { + const discount = this.promoCodes[this.state.promoCode.toUpperCase()]; + const discountPercent = Math.round((1 - discount) * 100); + promoCode.textContent = this.state.promoCode.toUpperCase(); + promoDiscount.textContent = `-${discountPercent}%`; + appliedPromoDiv.classList.remove('hidden'); + } else if (appliedPromoDiv) { + appliedPromoDiv.classList.add('hidden'); + } + + // Final calculation + const finalCalculation = document.getElementById('finalCalculation'); + if (finalCalculation) { + const price = this.calculatePrice(); + finalCalculation.textContent = this.formatPrice(price); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/.history/public/js/calculator_20251019182236.js b/.history/public/js/calculator_20251019182236.js deleted file mode 100644 index a9f0e01..0000000 --- a/.history/public/js/calculator_20251019182236.js +++ /dev/null @@ -1,368 +0,0 @@ -// 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 += '
Selected Services:
'; - this.selectedServices.forEach(service => { - summaryHTML += `
- - ${this.getServiceName(service.service)} - ₩${service.price.toLocaleString()} -
`; - }); - - // Complexity - if (this.selectedComplexity) { - summaryHTML += `
- - Complexity: - ${this.getComplexityName(this.selectedComplexity.value)} - ×${this.selectedComplexity.multiplier} -
`; - } - - // Timeline - if (this.selectedTimeline) { - summaryHTML += `
- - Timeline: - ${this.getTimelineName(this.selectedTimeline.value)} - ×${this.selectedTimeline.multiplier} -
`; - } - - // 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(); - } -}); \ No newline at end of file diff --git a/.history/public/js/calculator_20251019182338.js b/.history/public/js/calculator_20251019182338.js deleted file mode 100644 index 888cde0..0000000 --- a/.history/public/js/calculator_20251019182338.js +++ /dev/null @@ -1,380 +0,0 @@ -// 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 += '
Selected Services:
'; - this.selectedServices.forEach(service => { - summaryHTML += `
- - ${this.getServiceName(service.service)} - ₩${service.price.toLocaleString()} -
`; - }); - - // Complexity - if (this.selectedComplexity) { - summaryHTML += `
- - Complexity: - ${this.getComplexityName(this.selectedComplexity.value)} - ×${this.selectedComplexity.multiplier} -
`; - } - - // Timeline - if (this.selectedTimeline) { - summaryHTML += `
- - Timeline: - ${this.getTimelineName(this.selectedTimeline.value)} - ×${this.selectedTimeline.multiplier} -
`; - } - - // 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(); - } -}); \ No newline at end of file diff --git a/.history/public/js/calculator_20251019182400.js b/.history/public/js/calculator_20251019182400.js deleted file mode 100644 index 5618dd2..0000000 --- a/.history/public/js/calculator_20251019182400.js +++ /dev/null @@ -1,384 +0,0 @@ -// 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 += `
${servicesLabel}:
`; - this.selectedServices.forEach(service => { - summaryHTML += `
- - ${this.getServiceName(service.service)} - ₩${service.price.toLocaleString()} -
`; - }); - - // Complexity - if (this.selectedComplexity) { - summaryHTML += `
- - ${complexityLabel}: - ${this.getComplexityName(this.selectedComplexity.value)} - ×${this.selectedComplexity.multiplier} -
`; - } - - // Timeline - if (this.selectedTimeline) { - summaryHTML += `
- - ${timelineLabel}: - ${this.getTimelineName(this.selectedTimeline.value)} - ×${this.selectedTimeline.multiplier} -
`; - } - - // 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(); - } -}); \ No newline at end of file diff --git a/.history/public/js/calculator_20251019182510.js b/.history/public/js/calculator_20251019182510.js deleted file mode 100644 index 8bbbfcb..0000000 --- a/.history/public/js/calculator_20251019182510.js +++ /dev/null @@ -1,384 +0,0 @@ -// 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 += `
${servicesLabel}:
`; - this.selectedServices.forEach(service => { - summaryHTML += `
- - ${this.getServiceName(service.service)} - ₩${service.price.toLocaleString()} -
`; - }); - - // Complexity - if (this.selectedComplexity) { - summaryHTML += `
- - ${complexityLabel}: - ${this.getComplexityName(this.selectedComplexity.value)} - ×${this.selectedComplexity.multiplier} -
`; - } - - // Timeline - if (this.selectedTimeline) { - summaryHTML += `
- - ${timelineLabel}: - ${this.getTimelineName(this.selectedTimeline.value)} - ×${this.selectedTimeline.multiplier} -
`; - } - - // 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(); - } -}); \ No newline at end of file diff --git a/.history/public/js/calculator_20251019182628.js b/.history/public/js/calculator_20251019182628.js deleted file mode 100644 index 8bbbfcb..0000000 --- a/.history/public/js/calculator_20251019182628.js +++ /dev/null @@ -1,384 +0,0 @@ -// 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 += `
${servicesLabel}:
`; - this.selectedServices.forEach(service => { - summaryHTML += `
- - ${this.getServiceName(service.service)} - ₩${service.price.toLocaleString()} -
`; - }); - - // Complexity - if (this.selectedComplexity) { - summaryHTML += `
- - ${complexityLabel}: - ${this.getComplexityName(this.selectedComplexity.value)} - ×${this.selectedComplexity.multiplier} -
`; - } - - // Timeline - if (this.selectedTimeline) { - summaryHTML += `
- - ${timelineLabel}: - ${this.getTimelineName(this.selectedTimeline.value)} - ×${this.selectedTimeline.multiplier} -
`; - } - - // 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(); - } -}); \ No newline at end of file diff --git a/.history/public/js/debug-calculator_20251026101421.js b/.history/public/js/debug-calculator_20251026101421.js new file mode 100644 index 0000000..6ebae81 --- /dev/null +++ b/.history/public/js/debug-calculator_20251026101421.js @@ -0,0 +1,52 @@ +// Отладочная версия калькулятора +console.log('Debug calculator loaded'); + +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM loaded'); + + // Проверим наличие элементов + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + console.log('priceDisplay:', priceDisplay); + console.log('mobilePriceDisplay:', mobilePriceDisplay); + + if (priceDisplay) { + console.log('priceDisplay classes:', priceDisplay.className); + + // Тестируем показ блока + setTimeout(() => { + console.log('Showing price display...'); + priceDisplay.classList.remove('hidden'); + + // Обновляем цену + const currentPrice = document.getElementById('currentPrice'); + if (currentPrice) { + currentPrice.textContent = '₩500,000'; + } + }, 2000); + } + + // Добавим обработчики для карточек сервисов + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + console.log('Service card clicked:', card.dataset.service); + + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + const currentPrice = document.getElementById('currentPrice'); + if (currentPrice) { + currentPrice.textContent = '₩750,000'; + } + } + + if (mobilePriceDisplay) { + mobilePriceDisplay.classList.remove('hidden'); + const mobilePriceValue = document.getElementById('mobilePriceValue'); + if (mobilePriceValue) { + mobilePriceValue.textContent = '₩750,000'; + } + } + }); + }); +}); \ No newline at end of file diff --git a/.history/public/js/debug-calculator_20251026101507.js b/.history/public/js/debug-calculator_20251026101507.js new file mode 100644 index 0000000..6ebae81 --- /dev/null +++ b/.history/public/js/debug-calculator_20251026101507.js @@ -0,0 +1,52 @@ +// Отладочная версия калькулятора +console.log('Debug calculator loaded'); + +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM loaded'); + + // Проверим наличие элементов + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + console.log('priceDisplay:', priceDisplay); + console.log('mobilePriceDisplay:', mobilePriceDisplay); + + if (priceDisplay) { + console.log('priceDisplay classes:', priceDisplay.className); + + // Тестируем показ блока + setTimeout(() => { + console.log('Showing price display...'); + priceDisplay.classList.remove('hidden'); + + // Обновляем цену + const currentPrice = document.getElementById('currentPrice'); + if (currentPrice) { + currentPrice.textContent = '₩500,000'; + } + }, 2000); + } + + // Добавим обработчики для карточек сервисов + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + console.log('Service card clicked:', card.dataset.service); + + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + const currentPrice = document.getElementById('currentPrice'); + if (currentPrice) { + currentPrice.textContent = '₩750,000'; + } + } + + if (mobilePriceDisplay) { + mobilePriceDisplay.classList.remove('hidden'); + const mobilePriceValue = document.getElementById('mobilePriceValue'); + if (mobilePriceValue) { + mobilePriceValue.textContent = '₩750,000'; + } + } + }); + }); +}); \ No newline at end of file diff --git a/.history/public/js/main_20251019161602.js b/.history/public/js/main_20251026074914.js similarity index 89% rename from .history/public/js/main_20251019161602.js rename to .history/public/js/main_20251026074914.js index 0f44325..73b365f 100644 --- a/.history/public/js/main_20251019161602.js +++ b/.history/public/js/main_20251026074914.js @@ -10,6 +10,68 @@ document.addEventListener('DOMContentLoaded', function() { }); } + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const currentTheme = localTheme || serverTheme || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + + // Apply theme and update toggle + function applyTheme(isDark) { + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + slider.style.transform = 'translateX(24px)'; + slider.style.backgroundColor = '#374151'; + } + if (sunIcon) sunIcon.classList.add('hidden'); + if (moonIcon) moonIcon.classList.remove('hidden'); + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + slider.style.backgroundColor = '#ffffff'; + } + if (sunIcon) sunIcon.classList.remove('hidden'); + if (moonIcon) moonIcon.classList.add('hidden'); + } + } + + // Initial theme application + applyTheme(currentTheme === 'dark'); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + applyTheme(!isDark); + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + } + // Mobile Navigation Toggle const mobileMenuButton = document.querySelector('.mobile-menu-button'); const mobileMenu = document.querySelector('.mobile-menu'); diff --git a/.history/public/js/main_20251019162545.js b/.history/public/js/main_20251026092019.js similarity index 89% rename from .history/public/js/main_20251019162545.js rename to .history/public/js/main_20251026092019.js index 0f44325..73b365f 100644 --- a/.history/public/js/main_20251019162545.js +++ b/.history/public/js/main_20251026092019.js @@ -10,6 +10,68 @@ document.addEventListener('DOMContentLoaded', function() { }); } + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const currentTheme = localTheme || serverTheme || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + + // Apply theme and update toggle + function applyTheme(isDark) { + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + slider.style.transform = 'translateX(24px)'; + slider.style.backgroundColor = '#374151'; + } + if (sunIcon) sunIcon.classList.add('hidden'); + if (moonIcon) moonIcon.classList.remove('hidden'); + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + slider.style.backgroundColor = '#ffffff'; + } + if (sunIcon) sunIcon.classList.remove('hidden'); + if (moonIcon) moonIcon.classList.add('hidden'); + } + } + + // Initial theme application + applyTheme(currentTheme === 'dark'); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + applyTheme(!isDark); + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + } + // Mobile Navigation Toggle const mobileMenuButton = document.querySelector('.mobile-menu-button'); const mobileMenu = document.querySelector('.mobile-menu'); diff --git a/.history/public/js/main_20251026092237.js b/.history/public/js/main_20251026092237.js new file mode 100644 index 0000000..5489594 --- /dev/null +++ b/.history/public/js/main_20251026092237.js @@ -0,0 +1,638 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const currentTheme = localTheme || serverTheme || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = true) { + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + if (animate) { + // Smooth slide to right + slider.style.transform = 'translateX(28px)'; + slider.style.backgroundColor = '#374151'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Animated transition: sun rotates out, moon rotates in + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + if (animate) { + // Smooth slide to left + slider.style.transform = 'translateX(0)'; + slider.style.backgroundColor = '#ffffff'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Animated transition: moon rotates out, sun rotates in + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + } + + // Initial theme application + applyTheme(currentTheme === 'dark'); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + applyTheme(!isDark); + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092245.js b/.history/public/js/main_20251026092245.js new file mode 100644 index 0000000..9707208 --- /dev/null +++ b/.history/public/js/main_20251026092245.js @@ -0,0 +1,638 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const currentTheme = localTheme || serverTheme || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = true) { + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + if (animate) { + // Smooth slide to right + slider.style.transform = 'translateX(28px)'; + slider.style.backgroundColor = '#374151'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Animated transition: sun rotates out, moon rotates in + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + if (animate) { + // Smooth slide to left + slider.style.transform = 'translateX(0)'; + slider.style.backgroundColor = '#ffffff'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Animated transition: moon rotates out, sun rotates in + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092339.js b/.history/public/js/main_20251026092339.js new file mode 100644 index 0000000..cedef46 --- /dev/null +++ b/.history/public/js/main_20251026092339.js @@ -0,0 +1,662 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const currentTheme = localTheme || serverTheme || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = true) { + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + slider.style.transform = 'translateX(28px)'; + if (animate) { + // Smooth background color transition + slider.style.backgroundColor = '#374151'; + } else { + slider.style.backgroundColor = '#374151'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Remove any existing animation classes + sunIcon.classList.remove('icon-animate-in'); + moonIcon.classList.remove('icon-animate-in'); + + // Add animation classes + sunIcon.classList.add('icon-animate-out'); + moonIcon.classList.add('icon-animate-in'); + + // Set final states + setTimeout(() => { + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + }, 200); + } else { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + if (animate) { + // Smooth background color transition + slider.style.backgroundColor = '#ffffff'; + } else { + slider.style.backgroundColor = '#ffffff'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Remove any existing animation classes + sunIcon.classList.remove('icon-animate-out'); + moonIcon.classList.remove('icon-animate-out'); + + // Add animation classes + moonIcon.classList.add('icon-animate-out'); + sunIcon.classList.add('icon-animate-in'); + + // Set final states + setTimeout(() => { + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + }, 200); + } else { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092353.js b/.history/public/js/main_20251026092353.js new file mode 100644 index 0000000..cedef46 --- /dev/null +++ b/.history/public/js/main_20251026092353.js @@ -0,0 +1,662 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const currentTheme = localTheme || serverTheme || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = true) { + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + slider.style.transform = 'translateX(28px)'; + if (animate) { + // Smooth background color transition + slider.style.backgroundColor = '#374151'; + } else { + slider.style.backgroundColor = '#374151'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Remove any existing animation classes + sunIcon.classList.remove('icon-animate-in'); + moonIcon.classList.remove('icon-animate-in'); + + // Add animation classes + sunIcon.classList.add('icon-animate-out'); + moonIcon.classList.add('icon-animate-in'); + + // Set final states + setTimeout(() => { + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + }, 200); + } else { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + if (animate) { + // Smooth background color transition + slider.style.backgroundColor = '#ffffff'; + } else { + slider.style.backgroundColor = '#ffffff'; + } + } + if (sunIcon && moonIcon) { + if (animate) { + // Remove any existing animation classes + sunIcon.classList.remove('icon-animate-out'); + moonIcon.classList.remove('icon-animate-out'); + + // Add animation classes + moonIcon.classList.add('icon-animate-out'); + sunIcon.classList.add('icon-animate-in'); + + // Set final states + setTimeout(() => { + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + }, 200); + } else { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092625.js b/.history/public/js/main_20251026092625.js new file mode 100644 index 0000000..a866c84 --- /dev/null +++ b/.history/public/js/main_20251026092625.js @@ -0,0 +1,639 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + slider.style.transform = 'translateX(26px)'; // Adjusted for 6px slider + 1px border + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092650.js b/.history/public/js/main_20251026092650.js new file mode 100644 index 0000000..a866c84 --- /dev/null +++ b/.history/public/js/main_20251026092650.js @@ -0,0 +1,639 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + slider.style.transform = 'translateX(26px)'; // Adjusted for 6px slider + 1px border + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092900.js b/.history/public/js/main_20251026092900.js new file mode 100644 index 0000000..910f8f3 --- /dev/null +++ b/.history/public/js/main_20251026092900.js @@ -0,0 +1,640 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + // Расчет: ширина контейнера (56px) - ширина ползунка (20px) - отступы границ (4px) = 32px + slider.style.transform = 'translateX(32px)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026092941.js b/.history/public/js/main_20251026092941.js new file mode 100644 index 0000000..910f8f3 --- /dev/null +++ b/.history/public/js/main_20251026092941.js @@ -0,0 +1,640 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + // Расчет: ширина контейнера (56px) - ширина ползунка (20px) - отступы границ (4px) = 32px + slider.style.transform = 'translateX(32px)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026093130.js b/.history/public/js/main_20251026093130.js new file mode 100644 index 0000000..0e109a8 --- /dev/null +++ b/.history/public/js/main_20251026093130.js @@ -0,0 +1,640 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + // Расчет: ширина контейнера (56px) - ширина ползунка (20px) - отступы (8px) = 28px движения + slider.style.transform = 'translateX(28px)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/main_20251026093149.js b/.history/public/js/main_20251026093149.js new file mode 100644 index 0000000..0e109a8 --- /dev/null +++ b/.history/public/js/main_20251026093149.js @@ -0,0 +1,640 @@ +// 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 + }); + } + + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + // Расчет: ширина контейнера (56px) - ширина ползунка (20px) - отступы (8px) = 28px движения + slider.style.transform = 'translateX(28px)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + + // 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 + ? '' + : ''; + } + + // 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 = ` +
+ ${message} + +
+ `; + + // 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 = ` + + `; + + 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; +} \ No newline at end of file diff --git a/.history/public/js/price-island_20251026100040.js b/.history/public/js/price-island_20251026100040.js new file mode 100644 index 0000000..e87857f --- /dev/null +++ b/.history/public/js/price-island_20251026100040.js @@ -0,0 +1,289 @@ +// Dynamic Price Island Controller +class PriceIsland { + constructor() { + this.island = document.getElementById('priceIsland'); + this.container = document.getElementById('islandContainer'); + this.content = document.getElementById('islandContent'); + this.totalPrice = document.getElementById('totalPrice'); + + this.selectedServices = []; + this.projectDetails = {}; + this.appliedPromo = null; + this.isExpanded = false; + + this.init(); + } + + init() { + // Listen for calculator changes + document.addEventListener('calculatorStateChange', (e) => { + this.updateIsland(e.detail); + }); + + // Initialize promo code functionality + this.initPromoCode(); + + // Show island initially as compact + this.showCompact(); + } + + updateIsland(calculatorData) { + const { selectedServices, complexity, timeline, totalPrice } = calculatorData; + + this.selectedServices = selectedServices || []; + this.projectDetails = { complexity, timeline }; + + // Update total price + this.updateTotalPrice(totalPrice || 0); + + // Update content + this.updateContent(); + + // Expand if there's data to show + if (this.selectedServices.length > 0 || complexity || timeline) { + this.expand(); + } else { + this.collapse(); + } + } + + updateTotalPrice(price) { + if (this.totalPrice) { + this.totalPrice.textContent = this.formatPrice(price); + this.totalPrice.classList.add('price-update'); + setTimeout(() => { + this.totalPrice.classList.remove('price-update'); + }, 300); + } + } + + updateContent() { + this.updateSelectedServices(); + this.updateProjectDetails(); + this.updatePriceBreakdown(); + } + + updateSelectedServices() { + const container = document.getElementById('selectedServices'); + const list = document.getElementById('servicesList'); + + if (this.selectedServices.length > 0) { + container.classList.remove('hidden'); + list.innerHTML = this.selectedServices.map(service => ` +
+ ${service.name} + ${this.formatPrice(service.price)} +
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + updateProjectDetails() { + const container = document.getElementById('projectDetails'); + const list = document.getElementById('detailsList'); + + const details = []; + if (this.projectDetails.complexity) { + details.push(`Сложность: ${this.projectDetails.complexity.name} (×${this.projectDetails.complexity.multiplier})`); + } + if (this.projectDetails.timeline) { + details.push(`Сроки: ${this.projectDetails.timeline.name} (×${this.projectDetails.timeline.multiplier})`); + } + + if (details.length > 0) { + container.classList.remove('hidden'); + list.innerHTML = details.map(detail => ` +
${detail}
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + updatePriceBreakdown() { + const container = document.getElementById('priceBreakdown'); + const list = document.getElementById('breakdownList'); + + if (this.selectedServices.length > 0) { + container.classList.remove('hidden'); + + const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0); + const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1; + const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1; + + const afterComplexity = basePrice * complexityMultiplier; + const final = afterComplexity * timelineMultiplier; + + let breakdown = [ + { label: 'Базовая стоимость', value: basePrice } + ]; + + if (complexityMultiplier !== 1) { + breakdown.push({ + label: `Сложность (×${complexityMultiplier})`, + value: afterComplexity + }); + } + + if (timelineMultiplier !== 1) { + breakdown.push({ + label: `Сроки (×${timelineMultiplier})`, + value: final + }); + } + + if (this.appliedPromo) { + const discount = final * (this.appliedPromo.discount / 100); + breakdown.push({ + label: `Скидка ${this.appliedPromo.code} (-${this.appliedPromo.discount}%)`, + value: final - discount, + isDiscount: true + }); + } + + list.innerHTML = breakdown.map(item => ` +
+ ${item.label} + ${this.formatPrice(item.value)} +
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + initPromoCode() { + const promoBtn = document.getElementById('applyPromoBtn'); + const promoInput = document.getElementById('promoInput'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoBtn && promoInput) { + promoBtn.addEventListener('click', () => { + const code = promoInput.value.trim().toUpperCase(); + this.applyPromoCode(code); + }); + + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + const code = promoInput.value.trim().toUpperCase(); + this.applyPromoCode(code); + } + }); + } + } + + applyPromoCode(code) { + const promoStatus = document.getElementById('promoStatus'); + + // Simulate promo code validation + const validCodes = { + 'WELCOME10': { discount: 10, name: 'Скидка новичка' }, + 'SAVE20': { discount: 20, name: 'Экономия 20%' }, + 'VIP15': { discount: 15, name: 'VIP скидка' } + }; + + if (validCodes[code]) { + this.appliedPromo = { code, ...validCodes[code] }; + promoStatus.textContent = `✓ Применен: ${this.appliedPromo.name}`; + promoStatus.className = 'text-xs mt-1 text-green-600 dark:text-green-400'; + + // Trigger recalculation + this.updateContent(); + this.recalculateTotal(); + } else if (code) { + promoStatus.textContent = '✗ Неверный промокод'; + promoStatus.className = 'text-xs mt-1 text-red-600 dark:text-red-400'; + } else { + promoStatus.textContent = ''; + } + } + + recalculateTotal() { + if (this.selectedServices.length > 0) { + const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0); + const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1; + const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1; + + let total = basePrice * complexityMultiplier * timelineMultiplier; + + if (this.appliedPromo) { + total = total * (1 - this.appliedPromo.discount / 100); + } + + this.updateTotalPrice(total); + + // Also update mobile price if exists + const mobilePrice = document.getElementById('mobilePriceValue'); + if (mobilePrice) { + mobilePrice.textContent = this.formatPrice(total); + } + } + } + + expand() { + if (!this.isExpanded) { + this.isExpanded = true; + this.content.style.maxHeight = this.content.scrollHeight + 'px'; + + // Show promo section when expanded + const promoSection = document.getElementById('promoSection'); + if (promoSection) { + promoSection.classList.remove('hidden'); + } + } + } + + collapse() { + if (this.isExpanded) { + this.isExpanded = false; + this.content.style.maxHeight = '0'; + + // Hide promo section when collapsed + const promoSection = document.getElementById('promoSection'); + if (promoSection) { + promoSection.classList.add('hidden'); + } + } + } + + showCompact() { + this.island.classList.remove('hidden'); + } + + hide() { + this.island.classList.add('hidden'); + } + + formatPrice(price) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + maximumFractionDigits: 0 + }).format(price); + } +} + +// CSS for price update animation +const style = document.createElement('style'); +style.textContent = ` + .price-update { + animation: priceUpdate 0.3s ease-out; + } + + @keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } + } +`; +document.head.appendChild(style); + +// Initialize when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + window.priceIsland = new PriceIsland(); +}); + +// Export for use in calculator +window.PriceIsland = PriceIsland; \ No newline at end of file diff --git a/.history/public/js/price-island_20251026100441.js b/.history/public/js/price-island_20251026100441.js new file mode 100644 index 0000000..e87857f --- /dev/null +++ b/.history/public/js/price-island_20251026100441.js @@ -0,0 +1,289 @@ +// Dynamic Price Island Controller +class PriceIsland { + constructor() { + this.island = document.getElementById('priceIsland'); + this.container = document.getElementById('islandContainer'); + this.content = document.getElementById('islandContent'); + this.totalPrice = document.getElementById('totalPrice'); + + this.selectedServices = []; + this.projectDetails = {}; + this.appliedPromo = null; + this.isExpanded = false; + + this.init(); + } + + init() { + // Listen for calculator changes + document.addEventListener('calculatorStateChange', (e) => { + this.updateIsland(e.detail); + }); + + // Initialize promo code functionality + this.initPromoCode(); + + // Show island initially as compact + this.showCompact(); + } + + updateIsland(calculatorData) { + const { selectedServices, complexity, timeline, totalPrice } = calculatorData; + + this.selectedServices = selectedServices || []; + this.projectDetails = { complexity, timeline }; + + // Update total price + this.updateTotalPrice(totalPrice || 0); + + // Update content + this.updateContent(); + + // Expand if there's data to show + if (this.selectedServices.length > 0 || complexity || timeline) { + this.expand(); + } else { + this.collapse(); + } + } + + updateTotalPrice(price) { + if (this.totalPrice) { + this.totalPrice.textContent = this.formatPrice(price); + this.totalPrice.classList.add('price-update'); + setTimeout(() => { + this.totalPrice.classList.remove('price-update'); + }, 300); + } + } + + updateContent() { + this.updateSelectedServices(); + this.updateProjectDetails(); + this.updatePriceBreakdown(); + } + + updateSelectedServices() { + const container = document.getElementById('selectedServices'); + const list = document.getElementById('servicesList'); + + if (this.selectedServices.length > 0) { + container.classList.remove('hidden'); + list.innerHTML = this.selectedServices.map(service => ` +
+ ${service.name} + ${this.formatPrice(service.price)} +
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + updateProjectDetails() { + const container = document.getElementById('projectDetails'); + const list = document.getElementById('detailsList'); + + const details = []; + if (this.projectDetails.complexity) { + details.push(`Сложность: ${this.projectDetails.complexity.name} (×${this.projectDetails.complexity.multiplier})`); + } + if (this.projectDetails.timeline) { + details.push(`Сроки: ${this.projectDetails.timeline.name} (×${this.projectDetails.timeline.multiplier})`); + } + + if (details.length > 0) { + container.classList.remove('hidden'); + list.innerHTML = details.map(detail => ` +
${detail}
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + updatePriceBreakdown() { + const container = document.getElementById('priceBreakdown'); + const list = document.getElementById('breakdownList'); + + if (this.selectedServices.length > 0) { + container.classList.remove('hidden'); + + const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0); + const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1; + const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1; + + const afterComplexity = basePrice * complexityMultiplier; + const final = afterComplexity * timelineMultiplier; + + let breakdown = [ + { label: 'Базовая стоимость', value: basePrice } + ]; + + if (complexityMultiplier !== 1) { + breakdown.push({ + label: `Сложность (×${complexityMultiplier})`, + value: afterComplexity + }); + } + + if (timelineMultiplier !== 1) { + breakdown.push({ + label: `Сроки (×${timelineMultiplier})`, + value: final + }); + } + + if (this.appliedPromo) { + const discount = final * (this.appliedPromo.discount / 100); + breakdown.push({ + label: `Скидка ${this.appliedPromo.code} (-${this.appliedPromo.discount}%)`, + value: final - discount, + isDiscount: true + }); + } + + list.innerHTML = breakdown.map(item => ` +
+ ${item.label} + ${this.formatPrice(item.value)} +
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + initPromoCode() { + const promoBtn = document.getElementById('applyPromoBtn'); + const promoInput = document.getElementById('promoInput'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoBtn && promoInput) { + promoBtn.addEventListener('click', () => { + const code = promoInput.value.trim().toUpperCase(); + this.applyPromoCode(code); + }); + + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + const code = promoInput.value.trim().toUpperCase(); + this.applyPromoCode(code); + } + }); + } + } + + applyPromoCode(code) { + const promoStatus = document.getElementById('promoStatus'); + + // Simulate promo code validation + const validCodes = { + 'WELCOME10': { discount: 10, name: 'Скидка новичка' }, + 'SAVE20': { discount: 20, name: 'Экономия 20%' }, + 'VIP15': { discount: 15, name: 'VIP скидка' } + }; + + if (validCodes[code]) { + this.appliedPromo = { code, ...validCodes[code] }; + promoStatus.textContent = `✓ Применен: ${this.appliedPromo.name}`; + promoStatus.className = 'text-xs mt-1 text-green-600 dark:text-green-400'; + + // Trigger recalculation + this.updateContent(); + this.recalculateTotal(); + } else if (code) { + promoStatus.textContent = '✗ Неверный промокод'; + promoStatus.className = 'text-xs mt-1 text-red-600 dark:text-red-400'; + } else { + promoStatus.textContent = ''; + } + } + + recalculateTotal() { + if (this.selectedServices.length > 0) { + const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0); + const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1; + const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1; + + let total = basePrice * complexityMultiplier * timelineMultiplier; + + if (this.appliedPromo) { + total = total * (1 - this.appliedPromo.discount / 100); + } + + this.updateTotalPrice(total); + + // Also update mobile price if exists + const mobilePrice = document.getElementById('mobilePriceValue'); + if (mobilePrice) { + mobilePrice.textContent = this.formatPrice(total); + } + } + } + + expand() { + if (!this.isExpanded) { + this.isExpanded = true; + this.content.style.maxHeight = this.content.scrollHeight + 'px'; + + // Show promo section when expanded + const promoSection = document.getElementById('promoSection'); + if (promoSection) { + promoSection.classList.remove('hidden'); + } + } + } + + collapse() { + if (this.isExpanded) { + this.isExpanded = false; + this.content.style.maxHeight = '0'; + + // Hide promo section when collapsed + const promoSection = document.getElementById('promoSection'); + if (promoSection) { + promoSection.classList.add('hidden'); + } + } + } + + showCompact() { + this.island.classList.remove('hidden'); + } + + hide() { + this.island.classList.add('hidden'); + } + + formatPrice(price) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + maximumFractionDigits: 0 + }).format(price); + } +} + +// CSS for price update animation +const style = document.createElement('style'); +style.textContent = ` + .price-update { + animation: priceUpdate 0.3s ease-out; + } + + @keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } + } +`; +document.head.appendChild(style); + +// Initialize when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + window.priceIsland = new PriceIsland(); +}); + +// Export for use in calculator +window.PriceIsland = PriceIsland; \ No newline at end of file diff --git a/.history/public/js/sticky-price_20251026093525.js b/.history/public/js/sticky-price_20251026093525.js new file mode 100644 index 0000000..ee6c3ea --- /dev/null +++ b/.history/public/js/sticky-price_20251026093525.js @@ -0,0 +1,107 @@ +// Enhanced Sticky Price Display +document.addEventListener('DOMContentLoaded', function() { + const priceDisplay = document.getElementById('priceDisplay'); + const currentPrice = document.getElementById('currentPrice'); + + if (!priceDisplay) return; + + // Show price display when calculator starts + function showPriceDisplay() { + if (priceDisplay.classList.contains('hidden')) { + priceDisplay.classList.remove('hidden'); + priceDisplay.classList.add('visible'); + } + } + + // Enhanced scroll behavior + let scrollTimeout; + window.addEventListener('scroll', function() { + const scrollY = window.scrollY; + + // Show/hide based on scroll position + if (scrollY > 100) { + showPriceDisplay(); + priceDisplay.classList.add('scrolled'); + } else { + priceDisplay.classList.remove('scrolled'); + } + + // Clear previous timeout + clearTimeout(scrollTimeout); + + // Add subtle bounce effect + scrollTimeout = setTimeout(() => { + priceDisplay.style.transform = 'translateX(0) scale(1)'; + }, 150); + }); + + // Price update animation + const originalUpdatePrice = window.updatePrice || function() {}; + window.updatePrice = function(price, animate = true) { + if (currentPrice && animate) { + // Add updating animation + currentPrice.classList.add('updating'); + + // Update price after brief delay + setTimeout(() => { + currentPrice.textContent = price; + currentPrice.classList.remove('updating'); + }, 150); + } else if (currentPrice) { + currentPrice.textContent = price; + } + + // Show price display when price is calculated + showPriceDisplay(); + + // Call original function if it exists + if (typeof originalUpdatePrice === 'function') { + originalUpdatePrice(price, animate); + } + }; + + // Monitor calculator interactions + const calculator = document.querySelector('[data-calculator]') || document.querySelector('.calculator-container'); + if (calculator) { + // Show price display when user interacts with calculator + const inputs = calculator.querySelectorAll('input, select, button'); + inputs.forEach(input => { + input.addEventListener('change', showPriceDisplay); + input.addEventListener('click', showPriceDisplay); + }); + } + + // Enhanced visibility on mobile + function handleMobileVisibility() { + if (window.innerWidth <= 1024) { + priceDisplay.classList.remove('fixed'); + priceDisplay.classList.add('relative'); + } else { + priceDisplay.classList.remove('relative'); + priceDisplay.classList.add('fixed'); + } + } + + // Initial check and resize listener + handleMobileVisibility(); + window.addEventListener('resize', handleMobileVisibility); + + // Smooth reveal animation + setTimeout(() => { + if (priceDisplay && !priceDisplay.classList.contains('hidden')) { + priceDisplay.style.transition = 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)'; + } + }, 100); + + // Auto-show after initial page load + setTimeout(showPriceDisplay, 1000); +}); + +// Helper function for other scripts to trigger price display +window.showStickyPrice = function() { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + priceDisplay.classList.add('visible'); + } +}; \ No newline at end of file diff --git a/.history/public/js/sticky-price_20251026093548.js b/.history/public/js/sticky-price_20251026093548.js new file mode 100644 index 0000000..ee6c3ea --- /dev/null +++ b/.history/public/js/sticky-price_20251026093548.js @@ -0,0 +1,107 @@ +// Enhanced Sticky Price Display +document.addEventListener('DOMContentLoaded', function() { + const priceDisplay = document.getElementById('priceDisplay'); + const currentPrice = document.getElementById('currentPrice'); + + if (!priceDisplay) return; + + // Show price display when calculator starts + function showPriceDisplay() { + if (priceDisplay.classList.contains('hidden')) { + priceDisplay.classList.remove('hidden'); + priceDisplay.classList.add('visible'); + } + } + + // Enhanced scroll behavior + let scrollTimeout; + window.addEventListener('scroll', function() { + const scrollY = window.scrollY; + + // Show/hide based on scroll position + if (scrollY > 100) { + showPriceDisplay(); + priceDisplay.classList.add('scrolled'); + } else { + priceDisplay.classList.remove('scrolled'); + } + + // Clear previous timeout + clearTimeout(scrollTimeout); + + // Add subtle bounce effect + scrollTimeout = setTimeout(() => { + priceDisplay.style.transform = 'translateX(0) scale(1)'; + }, 150); + }); + + // Price update animation + const originalUpdatePrice = window.updatePrice || function() {}; + window.updatePrice = function(price, animate = true) { + if (currentPrice && animate) { + // Add updating animation + currentPrice.classList.add('updating'); + + // Update price after brief delay + setTimeout(() => { + currentPrice.textContent = price; + currentPrice.classList.remove('updating'); + }, 150); + } else if (currentPrice) { + currentPrice.textContent = price; + } + + // Show price display when price is calculated + showPriceDisplay(); + + // Call original function if it exists + if (typeof originalUpdatePrice === 'function') { + originalUpdatePrice(price, animate); + } + }; + + // Monitor calculator interactions + const calculator = document.querySelector('[data-calculator]') || document.querySelector('.calculator-container'); + if (calculator) { + // Show price display when user interacts with calculator + const inputs = calculator.querySelectorAll('input, select, button'); + inputs.forEach(input => { + input.addEventListener('change', showPriceDisplay); + input.addEventListener('click', showPriceDisplay); + }); + } + + // Enhanced visibility on mobile + function handleMobileVisibility() { + if (window.innerWidth <= 1024) { + priceDisplay.classList.remove('fixed'); + priceDisplay.classList.add('relative'); + } else { + priceDisplay.classList.remove('relative'); + priceDisplay.classList.add('fixed'); + } + } + + // Initial check and resize listener + handleMobileVisibility(); + window.addEventListener('resize', handleMobileVisibility); + + // Smooth reveal animation + setTimeout(() => { + if (priceDisplay && !priceDisplay.classList.contains('hidden')) { + priceDisplay.style.transition = 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)'; + } + }, 100); + + // Auto-show after initial page load + setTimeout(showPriceDisplay, 1000); +}); + +// Helper function for other scripts to trigger price display +window.showStickyPrice = function() { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + priceDisplay.classList.add('visible'); + } +}; \ No newline at end of file diff --git a/.history/public/manifest_20251019161703.json b/.history/public/manifest_20251019161703.json deleted file mode 100644 index c493fcf..0000000 --- a/.history/public/manifest_20251019161703.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "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/*"] - } - ] - } - } -} \ No newline at end of file diff --git a/.history/public/manifest_20251019162545.json b/.history/public/manifest_20251019162545.json deleted file mode 100644 index c493fcf..0000000 --- a/.history/public/manifest_20251019162545.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "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/*"] - } - ] - } - } -} \ No newline at end of file diff --git a/.history/public/sw_20251019161644.js b/.history/public/sw_20251019161644.js deleted file mode 100644 index 4cc2eef..0000000 --- a/.history/public/sw_20251019161644.js +++ /dev/null @@ -1,396 +0,0 @@ -// 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 -}); \ No newline at end of file diff --git a/.history/public/sw_20251019162545.js b/.history/public/sw_20251019162545.js deleted file mode 100644 index 4cc2eef..0000000 --- a/.history/public/sw_20251019162545.js +++ /dev/null @@ -1,396 +0,0 @@ -// 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 -}); \ No newline at end of file diff --git a/.history/public/sw_20251020042616.js b/.history/public/sw_20251020042616.js deleted file mode 100644 index adeb970..0000000 --- a/.history/public/sw_20251020042616.js +++ /dev/null @@ -1,399 +0,0 @@ -// 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', - '/css/fixes.css', - '/css/dark-theme.css', - '/js/main.js', - '/images/logo.png', - '/images/icon-192x192.png', - '/images/icon-144x144.png', - '/manifest.json', - 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', - 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', - 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', - 'https://unpkg.com/aos@2.3.1/dist/aos.css', - 'https://unpkg.com/aos@2.3.1/dist/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 -}); \ No newline at end of file diff --git a/.history/public/sw_20251020042624.js b/.history/public/sw_20251020042624.js deleted file mode 100644 index 9c6821c..0000000 --- a/.history/public/sw_20251020042624.js +++ /dev/null @@ -1,399 +0,0 @@ -// Service Worker for SmartSolTech PWA -const CACHE_NAME = 'smartsoltech-v1.0.1'; -const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.1'; -const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.1'; - -// Files to cache immediately -const STATIC_FILES = [ - '/', - '/css/main.css', - '/css/fixes.css', - '/css/dark-theme.css', - '/js/main.js', - '/images/logo.png', - '/images/icon-192x192.png', - '/images/icon-144x144.png', - '/manifest.json', - 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', - 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', - 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', - 'https://unpkg.com/aos@2.3.1/dist/aos.css', - 'https://unpkg.com/aos@2.3.1/dist/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 -}); \ No newline at end of file diff --git a/.history/public/sw_20251020042633.js b/.history/public/sw_20251020042633.js deleted file mode 100644 index 9c6821c..0000000 --- a/.history/public/sw_20251020042633.js +++ /dev/null @@ -1,399 +0,0 @@ -// Service Worker for SmartSolTech PWA -const CACHE_NAME = 'smartsoltech-v1.0.1'; -const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.1'; -const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.1'; - -// Files to cache immediately -const STATIC_FILES = [ - '/', - '/css/main.css', - '/css/fixes.css', - '/css/dark-theme.css', - '/js/main.js', - '/images/logo.png', - '/images/icon-192x192.png', - '/images/icon-144x144.png', - '/manifest.json', - 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', - 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', - 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', - 'https://unpkg.com/aos@2.3.1/dist/aos.css', - 'https://unpkg.com/aos@2.3.1/dist/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 -}); \ No newline at end of file diff --git a/.history/public/sw_20251021172445.js b/.history/public/sw_20251021172445.js deleted file mode 100644 index 94421c1..0000000 --- a/.history/public/sw_20251021172445.js +++ /dev/null @@ -1,407 +0,0 @@ -// Service Worker for SmartSolTech PWA -const CACHE_NAME = 'smartsoltech-v1.0.1'; -const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.1'; -const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.1'; - -// Files to cache immediately -const STATIC_FILES = [ - '/', - '/css/main.css', - '/css/fixes.css', - '/css/dark-theme.css', - '/js/main.js', - '/images/logo.png', - '/images/icon-192x192.png', - '/images/icon-144x144.png', - '/manifest.json', - 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', - 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', - 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', - 'https://unpkg.com/aos@2.3.1/dist/aos.css', - 'https://unpkg.com/aos@2.3.1/dist/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) { - try { - const cache = await caches.open(DYNAMIC_CACHE_NAME); - const cachedResponse = await cache.match(request); - - const fetchPromise = fetch(request).then(networkResponse => { - if (networkResponse && networkResponse.ok) { - cache.put(request, networkResponse.clone()); - } - return networkResponse; - }).catch(error => { - console.log('staleWhileRevalidate fetch failed:', error); - return null; - }); - - return cachedResponse || fetchPromise || new Response('Not available', { status: 503 }); - } catch (error) { - console.error('staleWhileRevalidate error:', error); - return new Response('Service unavailable', { status: 503 }); - } -} - -// 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 -}); \ No newline at end of file diff --git a/.history/public/sw_20251021172602.js b/.history/public/sw_20251021172602.js deleted file mode 100644 index 94421c1..0000000 --- a/.history/public/sw_20251021172602.js +++ /dev/null @@ -1,407 +0,0 @@ -// Service Worker for SmartSolTech PWA -const CACHE_NAME = 'smartsoltech-v1.0.1'; -const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.1'; -const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.1'; - -// Files to cache immediately -const STATIC_FILES = [ - '/', - '/css/main.css', - '/css/fixes.css', - '/css/dark-theme.css', - '/js/main.js', - '/images/logo.png', - '/images/icon-192x192.png', - '/images/icon-144x144.png', - '/manifest.json', - 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', - 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', - 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', - 'https://unpkg.com/aos@2.3.1/dist/aos.css', - 'https://unpkg.com/aos@2.3.1/dist/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) { - try { - const cache = await caches.open(DYNAMIC_CACHE_NAME); - const cachedResponse = await cache.match(request); - - const fetchPromise = fetch(request).then(networkResponse => { - if (networkResponse && networkResponse.ok) { - cache.put(request, networkResponse.clone()); - } - return networkResponse; - }).catch(error => { - console.log('staleWhileRevalidate fetch failed:', error); - return null; - }); - - return cachedResponse || fetchPromise || new Response('Not available', { status: 503 }); - } catch (error) { - console.error('staleWhileRevalidate error:', error); - return new Response('Service unavailable', { status: 503 }); - } -} - -// 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 -}); \ No newline at end of file diff --git a/.history/routes/admin_20251019160958.js b/.history/routes/admin_20251019160958.js deleted file mode 100644 index b4c8e82..0000000 --- a/.history/routes/admin_20251019160958.js +++ /dev/null @@ -1,387 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/admin_20251019162544.js b/.history/routes/admin_20251019162544.js deleted file mode 100644 index b4c8e82..0000000 --- a/.history/routes/admin_20251019162544.js +++ /dev/null @@ -1,387 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/admin_20251019202120.js b/.history/routes/admin_20251019202120.js deleted file mode 100644 index adec8e8..0000000 --- a/.history/routes/admin_20251019202120.js +++ /dev/null @@ -1,383 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019202126.js b/.history/routes/admin_20251019202126.js deleted file mode 100644 index 273444c..0000000 --- a/.history/routes/admin_20251019202126.js +++ /dev/null @@ -1,388 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019202132.js b/.history/routes/admin_20251019202132.js deleted file mode 100644 index 399d74a..0000000 --- a/.history/routes/admin_20251019202132.js +++ /dev/null @@ -1,388 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019202141.js b/.history/routes/admin_20251019202141.js deleted file mode 100644 index 06c7c64..0000000 --- a/.history/routes/admin_20251019202141.js +++ /dev/null @@ -1,395 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019202147.js b/.history/routes/admin_20251019202147.js deleted file mode 100644 index 65fa613..0000000 --- a/.history/routes/admin_20251019202147.js +++ /dev/null @@ -1,395 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019202631.js b/.history/routes/admin_20251019202631.js deleted file mode 100644 index 65fa613..0000000 --- a/.history/routes/admin_20251019202631.js +++ /dev/null @@ -1,395 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204203.js b/.history/routes/admin_20251019204203.js deleted file mode 100644 index cff7985..0000000 --- a/.history/routes/admin_20251019204203.js +++ /dev/null @@ -1,396 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204212.js b/.history/routes/admin_20251019204212.js deleted file mode 100644 index b15353e..0000000 --- a/.history/routes/admin_20251019204212.js +++ /dev/null @@ -1,396 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204224.js b/.history/routes/admin_20251019204224.js deleted file mode 100644 index e6abba0..0000000 --- a/.history/routes/admin_20251019204224.js +++ /dev/null @@ -1,397 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204235.js b/.history/routes/admin_20251019204235.js deleted file mode 100644 index 1682ba0..0000000 --- a/.history/routes/admin_20251019204235.js +++ /dev/null @@ -1,396 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204245.js b/.history/routes/admin_20251019204245.js deleted file mode 100644 index 29bd33c..0000000 --- a/.history/routes/admin_20251019204245.js +++ /dev/null @@ -1,398 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204257.js b/.history/routes/admin_20251019204257.js deleted file mode 100644 index 1c9c434..0000000 --- a/.history/routes/admin_20251019204257.js +++ /dev/null @@ -1,400 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204307.js b/.history/routes/admin_20251019204307.js deleted file mode 100644 index 3746273..0000000 --- a/.history/routes/admin_20251019204307.js +++ /dev/null @@ -1,400 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204323.js b/.history/routes/admin_20251019204323.js deleted file mode 100644 index d923515..0000000 --- a/.history/routes/admin_20251019204323.js +++ /dev/null @@ -1,400 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251019204805.js b/.history/routes/admin_20251019204805.js deleted file mode 100644 index d923515..0000000 --- a/.history/routes/admin_20251019204805.js +++ /dev/null @@ -1,400 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020044258.js b/.history/routes/admin_20251020044258.js deleted file mode 100644 index 8076f4d..0000000 --- a/.history/routes/admin_20251020044258.js +++ /dev/null @@ -1,399 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020044304.js b/.history/routes/admin_20251020044304.js deleted file mode 100644 index 5d00fdd..0000000 --- a/.history/routes/admin_20251020044304.js +++ /dev/null @@ -1,398 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020044311.js b/.history/routes/admin_20251020044311.js deleted file mode 100644 index a901d2e..0000000 --- a/.history/routes/admin_20251020044311.js +++ /dev/null @@ -1,397 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// 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.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020044317.js b/.history/routes/admin_20251020044317.js deleted file mode 100644 index ab8f4f0..0000000 --- a/.history/routes/admin_20251020044317.js +++ /dev/null @@ -1,402 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolio: portfolioCount, - services: servicesCount, - contacts: contactsCount, - unreadContacts: await Contact.count({ where: { 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020044325.js b/.history/routes/admin_20251020044325.js deleted file mode 100644 index 0006b12..0000000 --- a/.history/routes/admin_20251020044325.js +++ /dev/null @@ -1,402 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020044351.js b/.history/routes/admin_20251020044351.js deleted file mode 100644 index 0006b12..0000000 --- a/.history/routes/admin_20251020044351.js +++ /dev/null @@ -1,402 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020225524.js b/.history/routes/admin_20251020225524.js deleted file mode 100644 index 0c488a9..0000000 --- a/.history/routes/admin_20251020225524.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251020225526.js b/.history/routes/admin_20251020225526.js deleted file mode 100644 index 0c488a9..0000000 --- a/.history/routes/admin_20251020225526.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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: req.t('admin.login'), - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251021214310.js b/.history/routes/admin_20251021214310.js deleted file mode 100644 index f2e1b9f..0000000 --- a/.history/routes/admin_20251021214310.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: req.t('admin.login'), - error: req.t('errors.invalid_credentials') - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251021214318.js b/.history/routes/admin_20251021214318.js deleted file mode 100644 index 066db72..0000000 --- a/.history/routes/admin_20251021214318.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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: req.t('admin.login'), - error: req.t('errors.server_error') - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251021214326.js b/.history/routes/admin_20251021214326.js deleted file mode 100644 index c9ab8dc..0000000 --- a/.history/routes/admin_20251021214326.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: req.t('admin.dashboard'), - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251021214335.js b/.history/routes/admin_20251021214335.js deleted file mode 100644 index 219e18e..0000000 --- a/.history/routes/admin_20251021214335.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251021214352.js b/.history/routes/admin_20251021214352.js deleted file mode 100644 index 219e18e..0000000 --- a/.history/routes/admin_20251021214352.js +++ /dev/null @@ -1,422 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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; \ No newline at end of file diff --git a/.history/routes/admin_20251022052409.js b/.history/routes/admin_20251022052409.js deleted file mode 100644 index ea7dae9..0000000 --- a/.history/routes/admin_20251022052409.js +++ /dev/null @@ -1,501 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, (req, res) => { - const telegramService = require('../services/telegram'); - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled - }); -}); - -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - await telegramService.sendMessage(testMessage); - - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot - }); - } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -router.post('/telegram/send', requireAuth, async (req, res) => { - try { - const { message } = req.body; - - if (!message || message.trim().length === 0) { - return res.status(400).json({ - success: false, - message: 'Message is required' - }); - } - - const telegramService = require('../services/telegram'); - const result = await telegramService.sendMessage(message); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to send message' - }); - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022052452.js b/.history/routes/admin_20251022052452.js deleted file mode 100644 index ea7dae9..0000000 --- a/.history/routes/admin_20251022052452.js +++ /dev/null @@ -1,501 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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.findByPk(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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, (req, res) => { - const telegramService = require('../services/telegram'); - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled - }); -}); - -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - await telegramService.sendMessage(testMessage); - - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot - }); - } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -router.post('/telegram/send', requireAuth, async (req, res) => { - try { - const { message } = req.body; - - if (!message || message.trim().length === 0) { - return res.status(400).json({ - success: false, - message: 'Message is required' - }); - } - - const telegramService = require('../services/telegram'); - const result = await telegramService.sendMessage(message); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to send message' - }); - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022194710.js b/.history/routes/admin_20251022194710.js deleted file mode 100644 index 60e1cdf..0000000 --- a/.history/routes/admin_20251022194710.js +++ /dev/null @@ -1,717 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, (req, res) => { - const telegramService = require('../services/telegram'); - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled - }); -}); - -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - await telegramService.sendMessage(testMessage); - - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot - }); - } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -router.post('/telegram/send', requireAuth, async (req, res) => { - try { - const { message } = req.body; - - if (!message || message.trim().length === 0) { - return res.status(400).json({ - success: false, - message: 'Message is required' - }); - } - - const telegramService = require('../services/telegram'); - const result = await telegramService.sendMessage(message); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to send message' - }); - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022194741.js b/.history/routes/admin_20251022194741.js deleted file mode 100644 index 5d484dc..0000000 --- a/.history/routes/admin_20251022194741.js +++ /dev/null @@ -1,921 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, (req, res) => { - const telegramService = require('../services/telegram'); - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled - }); -}); - -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - await telegramService.sendMessage(testMessage); - - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot - }); - } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -router.post('/telegram/send', requireAuth, async (req, res) => { - try { - const { message } = req.body; - - if (!message || message.trim().length === 0) { - return res.status(400).json({ - success: false, - message: 'Message is required' - }); - } - - const telegramService = require('../services/telegram'); - const result = await telegramService.sendMessage(message); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to send message' - }); - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022194824.js b/.history/routes/admin_20251022194824.js deleted file mode 100644 index db9d7ba..0000000 --- a/.history/routes/admin_20251022194824.js +++ /dev/null @@ -1,921 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner Editor -router.get('/banner-editor', requireAuth, async (req, res) => { - try { - res.render('admin/banner-editor', { - title: 'Редактор Баннеров - Admin Panel', - layout: false, // Отключаем layout для этой страницы - user: req.session.user, - locale: req.getLocale() || 'ko', - theme: req.session.theme || 'light' - }); - } catch (error) { - console.error('Banner editor error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner editor' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, (req, res) => { - const telegramService = require('../services/telegram'); - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled - }); -}); - -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - await telegramService.sendMessage(testMessage); - - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot - }); - } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -router.post('/telegram/send', requireAuth, async (req, res) => { - try { - const { message } = req.body; - - if (!message || message.trim().length === 0) { - return res.status(400).json({ - success: false, - message: 'Message is required' - }); - } - - const telegramService = require('../services/telegram'); - const result = await telegramService.sendMessage(message); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to send message' - }); - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022194900.js b/.history/routes/admin_20251022194900.js deleted file mode 100644 index 435a517..0000000 --- a/.history/routes/admin_20251022194900.js +++ /dev/null @@ -1,1241 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, (req, res) => { - const telegramService = require('../services/telegram'); - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled - }); -}); - -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - await telegramService.sendMessage(testMessage); - - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot - }); - } else { - res.status(400).json({ - success: false, - message: result.message || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -router.post('/telegram/send', requireAuth, async (req, res) => { - try { - const { message } = req.body; - - if (!message || message.trim().length === 0) { - return res.status(400).json({ - success: false, - message: 'Message is required' - }); - } - - const telegramService = require('../services/telegram'); - const result = await telegramService.sendMessage(message); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to send message' - }); - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022195039.js b/.history/routes/admin_20251022195039.js deleted file mode 100644 index bb3e05f..0000000 --- a/.history/routes/admin_20251022195039.js +++ /dev/null @@ -1,1426 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022195541.js b/.history/routes/admin_20251022195541.js deleted file mode 100644 index 52890fe..0000000 --- a/.history/routes/admin_20251022195541.js +++ /dev/null @@ -1,1440 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022195905.js b/.history/routes/admin_20251022195905.js deleted file mode 100644 index 52890fe..0000000 --- a/.history/routes/admin_20251022195905.js +++ /dev/null @@ -1,1440 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022205401.js b/.history/routes/admin_20251022205401.js deleted file mode 100644 index f9ff0b1..0000000 --- a/.history/routes/admin_20251022205401.js +++ /dev/null @@ -1,1458 +0,0 @@ -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; -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// Configure multer for file uploads -const storage = multer.memoryStorage(); // Use memory storage to process with sharp -const upload = multer({ - storage, - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed'), false); - } - } -}); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'), -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished = false, - featured = false - } = req.body; - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) ? new Date() : null, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 생성되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022205440.js b/.history/routes/admin_20251022205440.js deleted file mode 100644 index 5b3798a..0000000 --- a/.history/routes/admin_20251022205440.js +++ /dev/null @@ -1,1504 +0,0 @@ -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; -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// Configure multer for file uploads -const storage = multer.memoryStorage(); // Use memory storage to process with sharp -const upload = multer({ - storage, - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed'), false); - } - } -}); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - if (parsedTechnologies.length === 0) { - return res.status(400).json({ - success: false, - message: 'Добавьте хотя бы одну технологию' - }); - } - - // Process uploaded images - const processedImages = []; - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - processedImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Create portfolio item - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: processedImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) ? new Date() : null, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно создан!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при создании проекта: ' + error.message - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - isPublished, - featured - } = req.body; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()), - demoUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: Boolean(isPublished), - featured: Boolean(featured), - publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: Boolean(isPublished) ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 업데이트되었습니다', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022205520.js b/.history/routes/admin_20251022205520.js deleted file mode 100644 index c4f4fe6..0000000 --- a/.history/routes/admin_20251022205520.js +++ /dev/null @@ -1,1552 +0,0 @@ -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; -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// Configure multer for file uploads -const storage = multer.memoryStorage(); // Use memory storage to process with sharp -const upload = multer({ - storage, - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed'), false); - } - } -}); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - if (parsedTechnologies.length === 0) { - return res.status(400).json({ - success: false, - message: 'Добавьте хотя бы одну технологию' - }); - } - - // Process uploaded images - const processedImages = []; - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - processedImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Create portfolio item - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: processedImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) ? new Date() : null, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно создан!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при создании проекта: ' + error.message - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: 'Проект не найден' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured, - existingImages - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - // Handle existing images - let finalImages = []; - try { - const existing = JSON.parse(existingImages || '[]'); - finalImages = Array.isArray(existing) ? existing : []; - } catch (e) { - finalImages = portfolio.images || []; - } - - // Process new uploaded images - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - finalImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = portfolio.seo || {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: finalImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно обновлен!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при обновлении проекта: ' + error.message - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022205549.js b/.history/routes/admin_20251022205549.js deleted file mode 100644 index 529109a..0000000 --- a/.history/routes/admin_20251022205549.js +++ /dev/null @@ -1,1610 +0,0 @@ -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; -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// Configure multer for file uploads -const storage = multer.memoryStorage(); // Use memory storage to process with sharp -const upload = multer({ - storage, - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed'), false); - } - } -}); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - if (parsedTechnologies.length === 0) { - return res.status(400).json({ - success: false, - message: 'Добавьте хотя бы одну технологию' - }); - } - - // Process uploaded images - const processedImages = []; - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - processedImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Create portfolio item - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: processedImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) ? new Date() : null, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно создан!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при создании проекта: ' + error.message - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: 'Проект не найден' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured, - existingImages - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - // Handle existing images - let finalImages = []; - try { - const existing = JSON.parse(existingImages || '[]'); - finalImages = Array.isArray(existing) ? existing : []; - } catch (e) { - finalImages = portfolio.images || []; - } - - // Process new uploaded images - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - finalImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = portfolio.seo || {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: finalImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно обновлен!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при обновлении проекта: ' + error.message - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Portfolio preview -router.post('/portfolio/preview', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName - } = req.body; - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - // Process uploaded images for preview - const previewImages = []; - if (req.files && req.files.length > 0) { - for (const file of req.files) { - // Convert to base64 for preview - const base64 = `data:${file.mimetype};base64,${file.buffer.toString('base64')}`; - previewImages.push(base64); - } - } - - const previewData = { - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: previewImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - createdAt: new Date() - }; - - res.json({ - success: true, - preview: previewData - }); - } catch (error) { - console.error('Portfolio preview error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при создании предпросмотра: ' + error.message - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/admin_20251022211821.js b/.history/routes/admin_20251022211821.js deleted file mode 100644 index 529109a..0000000 --- a/.history/routes/admin_20251022211821.js +++ /dev/null @@ -1,1610 +0,0 @@ -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; -const { body, validationResult } = require('express-validator'); -const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models'); - -// Configure multer for file uploads -const storage = multer.memoryStorage(); // Use memory storage to process with sharp -const upload = multer({ - storage, - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed'), false); - } - } -}); - -// 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', - error: null - }); -}); - -// Admin login POST -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ - where: { - email: email, - isActive: true - } - }); - if (!user || !(await user.comparePassword(password))) { - return res.render('admin/login', { - title: 'Admin Login', - error: 'Invalid credentials' - }); - } - - 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', - error: 'Server error' - }); - } -}); - -// Admin logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - console.error('Logout error:', err); - } - res.redirect('/admin/login'); - }); -}); - -// Dashboard (default route) -router.get('/', requireAuth, async (req, res) => { - res.redirect('/admin/dashboard'); -}); - -// Dashboard -router.get('/dashboard', requireAuth, async (req, res) => { - try { - const [ - portfolioCount, - servicesCount, - contactsCount, - recentContacts, - recentPortfolio - ] = await Promise.all([ - Portfolio.count({ where: { isPublished: true } }), - Service.count({ where: { isActive: true } }), - Contact.count(), - Contact.findAll({ - order: [['createdAt', 'DESC']], - limit: 5 - }), - Portfolio.findAll({ - where: { isPublished: true }, - order: [['createdAt', 'DESC']], - limit: 5 - }) - ]); - - const stats = { - portfolioCount: portfolioCount, - servicesCount: servicesCount, - contactsCount: contactsCount, - usersCount: await User.count() - }; - - res.render('admin/dashboard', { - title: 'Admin Dashboard', - 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' - }); - } -}); - -// Banner management -router.get('/banners', requireAuth, async (req, res) => { - try { - const page = parseInt(req.query.page) || 1; - const limit = 20; - const skip = (page - 1) * limit; - - const [banners, total] = await Promise.all([ - Banner.findAll({ - order: [['order', 'ASC'], ['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Banner.count() - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('admin/banners/list', { - title: 'Banner Management - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banners, - pagination: { - current: page, - total: totalPages, - hasNext: page < totalPages, - hasPrev: page > 1 - } - }); - } catch (error) { - console.error('Banner list error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banners' - }); - } -}); - -// Add banner -router.get('/banners/add', requireAuth, (req, res) => { - res.render('admin/banners/add', { - title: 'Add Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); -}); - -// Create banner -router.post('/banners/add', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive = true, - startDate, - endDate, - targetAudience = 'all', - backgroundColor, - textColor, - animation = 'none' - } = req.body; - - const banner = await Banner.create({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience, - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation - }); - - res.json({ - success: true, - message: '배너가 성공적으로 생성되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner creation error:', error); - res.status(500).json({ - success: false, - message: '배너 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit banner -router.get('/banners/edit/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Banner not found' - }); - } - - res.render('admin/banners/edit', { - title: 'Edit Banner - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - banner, - positions: [ - { value: 'hero', label: '메인 히어로' }, - { value: 'secondary', label: '보조 배너' }, - { value: 'footer', label: '푸터 배너' } - ], - animations: [ - { value: 'none', label: '없음' }, - { value: 'fade', label: '페이드' }, - { value: 'slide', label: '슬라이드' }, - { value: 'zoom', label: '줌' } - ] - }); - } catch (error) { - console.error('Banner edit error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading banner' - }); - } -}); - -// Update banner -router.put('/banners/:id', requireAuth, [ - body('title').notEmpty().withMessage('제목을 입력해주세요'), - body('position').isIn(['hero', 'secondary', 'footer']).withMessage('유효한 위치를 선택해주세요'), - body('order').isInt({ min: 0 }).withMessage('유효한 순서를 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const banner = await Banner.findByPk(req.params.id); - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const { - title, - subtitle, - description, - buttonText, - buttonUrl, - image, - mobileImage, - position, - order, - isActive, - startDate, - endDate, - targetAudience, - backgroundColor, - textColor, - animation - } = req.body; - - await banner.update({ - title, - subtitle: subtitle || null, - description: description || null, - buttonText: buttonText || null, - buttonUrl: buttonUrl || null, - image: image || null, - mobileImage: mobileImage || null, - position, - order: parseInt(order), - isActive: Boolean(isActive), - startDate: startDate || null, - endDate: endDate || null, - targetAudience: targetAudience || 'all', - backgroundColor: backgroundColor || null, - textColor: textColor || null, - animation: animation || 'none' - }); - - res.json({ - success: true, - message: '배너가 성공적으로 업데이트되었습니다', - banner: { - id: banner.id, - title: banner.title, - position: banner.position - } - }); - } catch (error) { - console.error('Banner update error:', error); - res.status(500).json({ - success: false, - message: '배너 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete banner -router.delete('/banners/:id', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.destroy(); - - res.json({ - success: true, - message: '배너가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Banner deletion error:', error); - res.status(500).json({ - success: false, - message: '배너 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle banner active status -router.patch('/banners/:id/toggle-active', requireAuth, async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - const newStatus = !banner.isActive; - await banner.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `배너가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Banner toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Record banner click -router.post('/banners/:id/click', async (req, res) => { - try { - const banner = await Banner.findByPk(req.params.id); - - if (!banner) { - return res.status(404).json({ - success: false, - message: '배너를 찾을 수 없습니다' - }); - } - - await banner.recordClick(); - - res.json({ - success: true, - clickCount: banner.clickCount - }); - } catch (error) { - console.error('Banner click record error:', error); - res.status(500).json({ - success: false, - message: '클릭 기록 중 오류가 발생했습니다' - }); - } -}); - -// Banner Editor (legacy route) -router.get('/banner-editor', requireAuth, async (req, res) => { - res.redirect('/admin/banners'); -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count() - ]); - - 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' - }); - } -}); - -// Utility function for category names -const getCategoryName = (category) => { - const categoryNames = { - 'web-development': 'Веб-разработка', - 'mobile-app': 'Мобильные приложения', - 'ui-ux-design': 'UI/UX дизайн', - 'e-commerce': 'Электронная коммерция', - 'enterprise': 'Корпоративное ПО', - 'other': 'Другое' - }; - return categoryNames[category] || category; -}; - -// 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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ], - getCategoryName: getCategoryName - }); -}); - -// Create portfolio item -router.post('/portfolio/add', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - if (parsedTechnologies.length === 0) { - return res.status(400).json({ - success: false, - message: 'Добавьте хотя бы одну технологию' - }); - } - - // Process uploaded images - const processedImages = []; - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - processedImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Create portfolio item - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: processedImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) ? new Date() : null, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно создан!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при создании проекта: ' + error.message - }); - } -}); - -// Edit portfolio item -router.get('/portfolio/edit/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(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, - categories: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'enterprise', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update portfolio item -router.put('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ - success: false, - message: 'Проект не найден' - }); - } - - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName, - duration, - seoTitle, - seoDescription, - isPublished, - featured, - existingImages - } = req.body; - - // Validate required fields - if (!title || !shortDescription || !description || !category) { - return res.status(400).json({ - success: false, - message: 'Заполните все обязательные поля' - }); - } - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - // Handle existing images - let finalImages = []; - try { - const existing = JSON.parse(existingImages || '[]'); - finalImages = Array.isArray(existing) ? existing : []; - } catch (e) { - finalImages = portfolio.images || []; - } - - // Process new uploaded images - if (req.files && req.files.length > 0) { - const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'portfolio'); - - // Ensure directory exists - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - for (const file of req.files) { - const timestamp = Date.now(); - const filename = `${timestamp}-${Math.random().toString(36).substr(2, 9)}.webp`; - const filepath = path.join(uploadDir, filename); - - // Process image with Sharp - await sharp(file.buffer) - .webp({ quality: 85 }) - .resize(1200, 800, { fit: 'inside', withoutEnlargement: true }) - .toFile(filepath); - - finalImages.push(`/uploads/portfolio/${filename}`); - } - } - - // Create SEO data - const seo = portfolio.seo || {}; - if (seoTitle) seo.title = seoTitle; - if (seoDescription) seo.description = seoDescription; - - // Update portfolio - await portfolio.update({ - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: finalImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - duration: duration ? parseInt(duration) : null, - isPublished: isPublished === 'true' || isPublished === true, - featured: featured === 'true' || featured === true, - publishedAt: (isPublished === 'true' || isPublished === true) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: (isPublished === 'true' || isPublished === true) ? 'published' : 'draft', - seo: Object.keys(seo).length > 0 ? seo : null - }); - - res.json({ - success: true, - message: 'Проект успешно обновлен!', - portfolio: { - id: portfolio.id, - title: portfolio.title, - category: portfolio.category, - isPublished: portfolio.isPublished - } - }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при обновлении проекта: ' + error.message - }); - } -}); - -// Delete portfolio item -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - await portfolio.destroy(); - - res.json({ - success: true, - message: '포트폴리오가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ - success: false, - message: '포트폴리오 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle portfolio publish status -router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - - if (!portfolio) { - return res.status(404).json({ - success: false, - message: '포트폴리오를 찾을 수 없습니다' - }); - } - - const newStatus = !portfolio.isPublished; - await portfolio.update({ - isPublished: newStatus, - publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt, - status: newStatus ? 'published' : 'draft' - }); - - res.json({ - success: true, - message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`, - isPublished: newStatus - }); - } catch (error) { - console.error('Portfolio toggle publish error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// Portfolio preview -router.post('/portfolio/preview', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - technologies, - demoUrl, - githubUrl, - clientName - } = req.body; - - // Parse technologies from JSON string - let parsedTechnologies = []; - try { - parsedTechnologies = JSON.parse(technologies || '[]'); - } catch (e) { - parsedTechnologies = typeof technologies === 'string' ? [technologies] : []; - } - - // Process uploaded images for preview - const previewImages = []; - if (req.files && req.files.length > 0) { - for (const file of req.files) { - // Convert to base64 for preview - const base64 = `data:${file.mimetype};base64,${file.buffer.toString('base64')}`; - previewImages.push(base64); - } - } - - const previewData = { - title, - shortDescription, - description, - category, - technologies: parsedTechnologies, - images: previewImages, - projectUrl: demoUrl || null, - githubUrl: githubUrl || null, - clientName: clientName || null, - createdAt: new Date() - }; - - res.json({ - success: true, - preview: previewData - }); - } catch (error) { - console.error('Portfolio preview error:', error); - res.status(500).json({ - success: false, - message: 'Ошибка при создании предпросмотра: ' + error.message - }); - } -}); - -// 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.findAll({ - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Service.count() - ]); - - 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, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); -}); - -// Create service -router.post('/services/add', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive = true, - featured = false - } = req.body; - - const service = await Service.create({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 생성되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ - success: false, - message: '서비스 생성 중 오류가 발생했습니다' - }); - } -}); - -// Edit service -router.get('/services/edit/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Service not found' - }); - } - - const availablePortfolio = await Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['id', 'title', 'category'] - }); - - res.render('admin/services/edit', { - title: 'Edit Service - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - service, - availablePortfolio, - serviceTypes: [ - 'web-development', - 'mobile-app', - 'ui-ux-design', - 'e-commerce', - 'seo', - 'maintenance', - 'consultation', - 'other' - ] - }); - } 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' - }); - } -}); - -// Update service -router.put('/services/:id', requireAuth, [ - body('name').notEmpty().withMessage('서비스명을 입력해주세요'), - body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'), - body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'), - body('category').notEmpty().withMessage('카테고리를 선택해주세요'), - body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: '입력 데이터를 확인해주세요', - errors: errors.array() - }); - } - - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const { - name, - shortDescription, - description, - category, - basePrice, - features, - duration, - isActive, - featured - } = req.body; - - await service.update({ - name, - shortDescription, - description, - category, - basePrice: parseFloat(basePrice), - features: features || [], - duration: duration ? parseInt(duration) : null, - isActive: Boolean(isActive), - featured: Boolean(featured) - }); - - res.json({ - success: true, - message: '서비스가 성공적으로 업데이트되었습니다', - service: { - id: service.id, - name: service.name, - category: service.category - } - }); - } catch (error) { - console.error('Service update error:', error); - res.status(500).json({ - success: false, - message: '서비스 업데이트 중 오류가 발생했습니다' - }); - } -}); - -// Delete service -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - await service.destroy(); - - res.json({ - success: true, - message: '서비스가 성공적으로 삭제되었습니다' - }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ - success: false, - message: '서비스 삭제 중 오류가 발생했습니다' - }); - } -}); - -// Toggle service active status -router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - - if (!service) { - return res.status(404).json({ - success: false, - message: '서비스를 찾을 수 없습니다' - }); - } - - const newStatus = !service.isActive; - await service.update({ isActive: newStatus }); - - res.json({ - success: true, - message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`, - isActive: newStatus - }); - } catch (error) { - console.error('Service toggle active error:', error); - res.status(500).json({ - success: false, - message: '상태 변경 중 오류가 발생했습니다' - }); - } -}); - -// 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 whereClause = {}; - if (status && status !== 'all') { - whereClause.status = status; - } - - const [contacts, total] = await Promise.all([ - Contact.findAll({ - where: whereClause, - order: [['createdAt', 'DESC']], - offset: skip, - limit: limit - }), - Contact.count({ where: whereClause }) - ]); - - 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.findByPk(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() || await SiteSettings.create({}); - - 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 - }); -}); - -// Telegram bot configuration and testing -router.get('/telegram', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - - // Get bot info and available chats if token is configured - let botInfo = null; - let availableChats = []; - - if (telegramService.botToken) { - const result = await telegramService.getBotInfo(); - if (result.success) { - botInfo = result.bot; - availableChats = telegramService.getAvailableChats(); - } - } - - res.render('admin/telegram', { - title: 'Telegram Bot - Admin Panel', - layout: 'admin/layout', - user: req.session.user, - botConfigured: telegramService.isEnabled, - botToken: telegramService.botToken || '', - chatId: telegramService.chatId || '', - botInfo, - availableChats - }); - } catch (error) { - console.error('Telegram page error:', error); - res.status(500).render('admin/error', { - title: 'Error - Admin Panel', - layout: 'admin/layout', - message: 'Error loading Telegram settings' - }); - } -}); - -// Update bot token -router.post('/telegram/configure', requireAuth, [ - body('botToken').notEmpty().withMessage('Bot token is required'), - body('chatId').optional().isNumeric().withMessage('Chat ID must be numeric') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { botToken, chatId } = req.body; - const telegramService = require('../services/telegram'); - - // Update bot token - const result = await telegramService.updateBotToken(botToken); - - if (result.success) { - // Update chat ID if provided - if (chatId) { - telegramService.updateChatId(parseInt(chatId)); - } - - // Update environment variables (in production, this should update a config file) - process.env.TELEGRAM_BOT_TOKEN = botToken; - if (chatId) { - process.env.TELEGRAM_CHAT_ID = chatId; - } - - res.json({ - success: true, - message: 'Telegram bot configured successfully', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to configure bot' - }); - } - } catch (error) { - console.error('Configure Telegram bot error:', error); - res.status(500).json({ - success: false, - message: 'Error configuring Telegram bot' - }); - } -}); - -// Get bot info and discover chats -router.get('/telegram/info', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - res.json({ - success: true, - botInfo: result.bot, - availableChats: result.availableChats || [], - isConfigured: telegramService.isEnabled - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to get bot info' - }); - } - } catch (error) { - console.error('Get Telegram info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting bot information' - }); - } -}); - -// Get chat information -router.get('/telegram/chat/:chatId', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.getChat(req.params.chatId); - - if (result.success) { - res.json({ - success: true, - chat: result.chat - }); - } else { - res.status(400).json({ - success: false, - message: result.error || 'Failed to get chat info' - }); - } - } catch (error) { - console.error('Get chat info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting chat information' - }); - } -}); - -// Test connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const telegramService = require('../services/telegram'); - const result = await telegramService.testConnection(); - - if (result.success) { - const testMessage = `🤖 Тест Telegram бота\n\n` + - `✅ Соединение успешно установлено!\n` + - `🤖 Бот: @${result.bot.username} (${result.bot.first_name})\n` + - `🆔 ID бота: ${result.bot.id}\n` + - `⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` + - `🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` + - `Бот готов к отправке уведомлений! 🚀`; - - const sendResult = await telegramService.sendMessage(testMessage); - - if (sendResult.success) { - res.json({ - success: true, - message: 'Test message sent successfully!', - botInfo: result.bot, - availableChats: result.availableChats || [] - }); - } else { - res.status(400).json({ - success: false, - message: 'Bot connection successful, but failed to send test message: ' + (sendResult.error || sendResult.message) - }); - } - } else { - res.status(400).json({ - success: false, - message: result.message || result.error || 'Failed to connect to Telegram bot' - }); - } - } catch (error) { - console.error('Telegram test error:', error); - res.status(500).json({ - success: false, - message: 'Error testing Telegram bot' - }); - } -}); - -// Send custom message -router.post('/telegram/send', requireAuth, [ - body('message').notEmpty().withMessage('Message is required'), - body('chatIds').optional().isArray().withMessage('Chat IDs must be an array'), - body('parseMode').optional().isIn(['HTML', 'Markdown', 'MarkdownV2']).withMessage('Invalid parse mode') -], async (req, res) => { - try { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - message: 'Validation failed', - errors: errors.array() - }); - } - - const { - message, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false - } = req.body; - - const telegramService = require('../services/telegram'); - - let result; - if (chatIds.length > 0) { - // Send to multiple chats - result = await telegramService.sendCustomMessage({ - text: message, - chatIds: chatIds.map(id => parseInt(id)), - parseMode, - disableWebPagePreview, - disableNotification - }); - - res.json({ - success: result.success, - message: result.success ? - `Message sent to ${result.totalSent} chat(s). ${result.totalFailed} failed.` : - 'Failed to send message', - results: result.results || [], - errors: result.errors || [] - }); - } else { - // Send to default chat - result = await telegramService.sendMessage(message, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification - }); - - if (result.success) { - res.json({ - success: true, - message: 'Message sent successfully!' - }); - } else { - res.status(400).json({ - success: false, - message: result.error || result.message || 'Failed to send message' - }); - } - } - } catch (error) { - console.error('Send Telegram message error:', error); - res.status(500).json({ - success: false, - message: 'Error sending message' - }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/api/admin_20251021213116.js b/.history/routes/api/admin_20251021213116.js deleted file mode 100644 index e0cd2cb..0000000 --- a/.history/routes/api/admin_20251021213116.js +++ /dev/null @@ -1,284 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const multer = require('multer'); -const path = require('path'); -const { Portfolio, Service, Contact, User } = require('../../models'); - -// Multer configuration for file uploads -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, 'public/uploads/'); - }, - filename: (req, file, cb) => { - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); - cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); - } -}); - -const upload = multer({ - storage: storage, - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed!'), false); - } - }, - limits: { - fileSize: 10 * 1024 * 1024 // 10MB - } -}); - -// Authentication middleware -const requireAuth = (req, res, next) => { - if (!req.session.user) { - return res.status(401).json({ success: false, message: 'Authentication required' }); - } - next(); -}; - -// Portfolio API Routes -router.post('/portfolio', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - clientName, - projectUrl, - githubUrl, - technologies, - featured, - isPublished - } = req.body; - - // Process uploaded images - const images = req.files ? req.files.map((file, index) => ({ - url: `/uploads/${file.filename}`, - alt: `${title} image ${index + 1}`, - isPrimary: index === 0 - })) : []; - - // Parse technologies - let techArray = []; - if (technologies) { - try { - techArray = JSON.parse(technologies); - } catch (e) { - techArray = technologies.split(',').map(t => t.trim()); - } - } - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - clientName, - projectUrl: projectUrl || null, - githubUrl: githubUrl || null, - technologies: techArray, - images, - featured: featured === 'on', - isPublished: isPublished === 'on', - status: 'completed', - publishedAt: isPublished === 'on' ? new Date() : null - }); - - res.json({ success: true, portfolio }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.patch('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ success: false, message: 'Portfolio not found' }); - } - - const updates = { ...req.body }; - - // Handle checkboxes - updates.featured = updates.featured === 'on'; - updates.isPublished = updates.isPublished === 'on'; - - // Process technologies - if (updates.technologies) { - try { - updates.technologies = JSON.parse(updates.technologies); - } catch (e) { - updates.technologies = updates.technologies.split(',').map(t => t.trim()); - } - } - - // Process new images - if (req.files && req.files.length > 0) { - const newImages = req.files.map((file, index) => ({ - url: `/uploads/${file.filename}`, - alt: `${updates.title || portfolio.title} image ${index + 1}`, - isPrimary: index === 0 && (!portfolio.images || portfolio.images.length === 0) - })); - - updates.images = [...(portfolio.images || []), ...newImages]; - } - - await portfolio.update(updates); - res.json({ success: true, portfolio }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ success: false, message: 'Portfolio not found' }); - } - - await portfolio.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Services API Routes -router.post('/services', requireAuth, async (req, res) => { - try { - const { - name, - description, - shortDescription, - icon, - category, - features, - pricing, - estimatedTime, - isActive, - featured, - tags - } = req.body; - - // Parse arrays - let featuresArray = []; - let tagsArray = []; - let pricingObj = {}; - - if (features) { - try { - featuresArray = JSON.parse(features); - } catch (e) { - featuresArray = features.split(',').map(f => f.trim()); - } - } - - if (tags) { - try { - tagsArray = JSON.parse(tags); - } catch (e) { - tagsArray = tags.split(',').map(t => t.trim()); - } - } - - if (pricing) { - try { - pricingObj = JSON.parse(pricing); - } catch (e) { - pricingObj = { basePrice: pricing }; - } - } - - const service = await Service.create({ - name, - description, - shortDescription, - icon, - category, - features: featuresArray, - pricing: pricingObj, - estimatedTime, - isActive: isActive === 'on', - featured: featured === 'on', - tags: tagsArray - }); - - res.json({ success: true, service }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ success: false, message: 'Service not found' }); - } - - await service.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Contacts API Routes -router.patch('/contacts/:id', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - await contact.update(req.body); - res.json({ success: true, contact }); - } catch (error) { - console.error('Contact update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/contacts/:id', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - await contact.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Contact deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Telegram notification for contact -router.post('/contacts/:id/telegram', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - // Send Telegram notification (we'll implement this with Telegram bot) - const telegramService = require('../../services/telegram'); - await telegramService.sendContactNotification(contact); - - res.json({ success: true }); - } catch (error) { - console.error('Telegram notification error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/api/admin_20251021213414.js b/.history/routes/api/admin_20251021213414.js deleted file mode 100644 index f7fc941..0000000 --- a/.history/routes/api/admin_20251021213414.js +++ /dev/null @@ -1,388 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const multer = require('multer'); -const path = require('path'); -const { Portfolio, Service, Contact, User } = require('../../models'); - -// Multer configuration for file uploads -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, 'public/uploads/'); - }, - filename: (req, file, cb) => { - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); - cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); - } -}); - -const upload = multer({ - storage: storage, - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed!'), false); - } - }, - limits: { - fileSize: 10 * 1024 * 1024 // 10MB - } -}); - -// Authentication middleware -const requireAuth = (req, res, next) => { - if (!req.session.user) { - return res.status(401).json({ success: false, message: 'Authentication required' }); - } - next(); -}; - -// Portfolio API Routes -router.post('/portfolio', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - clientName, - projectUrl, - githubUrl, - technologies, - featured, - isPublished - } = req.body; - - // Process uploaded images - const images = req.files ? req.files.map((file, index) => ({ - url: `/uploads/${file.filename}`, - alt: `${title} image ${index + 1}`, - isPrimary: index === 0 - })) : []; - - // Parse technologies - let techArray = []; - if (technologies) { - try { - techArray = JSON.parse(technologies); - } catch (e) { - techArray = technologies.split(',').map(t => t.trim()); - } - } - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - clientName, - projectUrl: projectUrl || null, - githubUrl: githubUrl || null, - technologies: techArray, - images, - featured: featured === 'on', - isPublished: isPublished === 'on', - status: 'completed', - publishedAt: isPublished === 'on' ? new Date() : null - }); - - res.json({ success: true, portfolio }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.patch('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ success: false, message: 'Portfolio not found' }); - } - - const updates = { ...req.body }; - - // Handle checkboxes - updates.featured = updates.featured === 'on'; - updates.isPublished = updates.isPublished === 'on'; - - // Process technologies - if (updates.technologies) { - try { - updates.technologies = JSON.parse(updates.technologies); - } catch (e) { - updates.technologies = updates.technologies.split(',').map(t => t.trim()); - } - } - - // Process new images - if (req.files && req.files.length > 0) { - const newImages = req.files.map((file, index) => ({ - url: `/uploads/${file.filename}`, - alt: `${updates.title || portfolio.title} image ${index + 1}`, - isPrimary: index === 0 && (!portfolio.images || portfolio.images.length === 0) - })); - - updates.images = [...(portfolio.images || []), ...newImages]; - } - - await portfolio.update(updates); - res.json({ success: true, portfolio }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ success: false, message: 'Portfolio not found' }); - } - - await portfolio.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Services API Routes -router.post('/services', requireAuth, async (req, res) => { - try { - const { - name, - description, - shortDescription, - icon, - category, - features, - pricing, - estimatedTime, - isActive, - featured, - tags - } = req.body; - - // Parse arrays - let featuresArray = []; - let tagsArray = []; - let pricingObj = {}; - - if (features) { - try { - featuresArray = JSON.parse(features); - } catch (e) { - featuresArray = features.split(',').map(f => f.trim()); - } - } - - if (tags) { - try { - tagsArray = JSON.parse(tags); - } catch (e) { - tagsArray = tags.split(',').map(t => t.trim()); - } - } - - if (pricing) { - try { - pricingObj = JSON.parse(pricing); - } catch (e) { - pricingObj = { basePrice: pricing }; - } - } - - const service = await Service.create({ - name, - description, - shortDescription, - icon, - category, - features: featuresArray, - pricing: pricingObj, - estimatedTime, - isActive: isActive === 'on', - featured: featured === 'on', - tags: tagsArray - }); - - res.json({ success: true, service }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ success: false, message: 'Service not found' }); - } - - await service.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Contacts API Routes -router.patch('/contacts/:id', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - await contact.update(req.body); - res.json({ success: true, contact }); - } catch (error) { - console.error('Contact update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/contacts/:id', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - await contact.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Contact deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Telegram notification for contact -router.post('/contacts/:id/telegram', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - // Send Telegram notification - const telegramService = require('../../services/telegram'); - const result = await telegramService.sendContactNotification(contact); - - if (result.success) { - res.json({ success: true }); - } else { - res.status(500).json({ success: false, message: result.message || result.error }); - } - } catch (error) { - console.error('Telegram notification error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Test Telegram connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const { botToken, chatId } = req.body; - - // Temporarily set up telegram service with provided credentials - const axios = require('axios'); - - // Test bot info - const botResponse = await axios.get(`https://api.telegram.org/bot${botToken}/getMe`); - - // Test sending a message - const testMessage = '✅ Telegram bot подключен успешно!\n\nЭто тестовое сообщение от SmartSolTech Admin Panel.'; - await axios.post(`https://api.telegram.org/bot${botToken}/sendMessage`, { - chat_id: chatId, - text: testMessage, - parse_mode: 'Markdown' - }); - - res.json({ - success: true, - bot: botResponse.data.result, - message: 'Test message sent successfully' - }); - } catch (error) { - console.error('Telegram test error:', error); - let message = 'Connection failed'; - - if (error.response?.data?.description) { - message = error.response.data.description; - } else if (error.message) { - message = error.message; - } - - res.status(400).json({ success: false, message }); - } -}); - -// Settings API -const { SiteSettings } = require('../../models'); - -router.get('/settings', requireAuth, async (req, res) => { - try { - const settings = await SiteSettings.findOne() || {}; - res.json({ success: true, settings }); - } catch (error) { - console.error('Settings fetch error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.post('/settings', requireAuth, upload.fields([ - { name: 'logo', maxCount: 1 }, - { name: 'favicon', maxCount: 1 } -]), async (req, res) => { - try { - let settings = await SiteSettings.findOne(); - if (!settings) { - settings = await SiteSettings.create({}); - } - - const updates = {}; - - // Handle nested objects - Object.keys(req.body).forEach(key => { - if (key.includes('.')) { - const [parent, child] = key.split('.'); - if (!updates[parent]) updates[parent] = {}; - updates[parent][child] = req.body[key]; - } else { - updates[key] = req.body[key]; - } - }); - - // Handle file uploads - if (req.files.logo) { - updates.logo = `/uploads/${req.files.logo[0].filename}`; - } - if (req.files.favicon) { - updates.favicon = `/uploads/${req.files.favicon[0].filename}`; - } - - // Update existing settings with new values - Object.keys(updates).forEach(key => { - if (typeof updates[key] === 'object' && updates[key] !== null) { - settings[key] = { ...settings[key], ...updates[key] }; - } else { - settings[key] = updates[key]; - } - }); - - await settings.save(); - - res.json({ success: true, settings }); - } catch (error) { - console.error('Settings update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/api/admin_20251021214113.js b/.history/routes/api/admin_20251021214113.js deleted file mode 100644 index f7fc941..0000000 --- a/.history/routes/api/admin_20251021214113.js +++ /dev/null @@ -1,388 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const multer = require('multer'); -const path = require('path'); -const { Portfolio, Service, Contact, User } = require('../../models'); - -// Multer configuration for file uploads -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, 'public/uploads/'); - }, - filename: (req, file, cb) => { - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); - cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); - } -}); - -const upload = multer({ - storage: storage, - fileFilter: (req, file, cb) => { - if (file.mimetype.startsWith('image/')) { - cb(null, true); - } else { - cb(new Error('Only image files are allowed!'), false); - } - }, - limits: { - fileSize: 10 * 1024 * 1024 // 10MB - } -}); - -// Authentication middleware -const requireAuth = (req, res, next) => { - if (!req.session.user) { - return res.status(401).json({ success: false, message: 'Authentication required' }); - } - next(); -}; - -// Portfolio API Routes -router.post('/portfolio', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const { - title, - shortDescription, - description, - category, - clientName, - projectUrl, - githubUrl, - technologies, - featured, - isPublished - } = req.body; - - // Process uploaded images - const images = req.files ? req.files.map((file, index) => ({ - url: `/uploads/${file.filename}`, - alt: `${title} image ${index + 1}`, - isPrimary: index === 0 - })) : []; - - // Parse technologies - let techArray = []; - if (technologies) { - try { - techArray = JSON.parse(technologies); - } catch (e) { - techArray = technologies.split(',').map(t => t.trim()); - } - } - - const portfolio = await Portfolio.create({ - title, - shortDescription, - description, - category, - clientName, - projectUrl: projectUrl || null, - githubUrl: githubUrl || null, - technologies: techArray, - images, - featured: featured === 'on', - isPublished: isPublished === 'on', - status: 'completed', - publishedAt: isPublished === 'on' ? new Date() : null - }); - - res.json({ success: true, portfolio }); - } catch (error) { - console.error('Portfolio creation error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.patch('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ success: false, message: 'Portfolio not found' }); - } - - const updates = { ...req.body }; - - // Handle checkboxes - updates.featured = updates.featured === 'on'; - updates.isPublished = updates.isPublished === 'on'; - - // Process technologies - if (updates.technologies) { - try { - updates.technologies = JSON.parse(updates.technologies); - } catch (e) { - updates.technologies = updates.technologies.split(',').map(t => t.trim()); - } - } - - // Process new images - if (req.files && req.files.length > 0) { - const newImages = req.files.map((file, index) => ({ - url: `/uploads/${file.filename}`, - alt: `${updates.title || portfolio.title} image ${index + 1}`, - isPrimary: index === 0 && (!portfolio.images || portfolio.images.length === 0) - })); - - updates.images = [...(portfolio.images || []), ...newImages]; - } - - await portfolio.update(updates); - res.json({ success: true, portfolio }); - } catch (error) { - console.error('Portfolio update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/portfolio/:id', requireAuth, async (req, res) => { - try { - const portfolio = await Portfolio.findByPk(req.params.id); - if (!portfolio) { - return res.status(404).json({ success: false, message: 'Portfolio not found' }); - } - - await portfolio.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Portfolio deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Services API Routes -router.post('/services', requireAuth, async (req, res) => { - try { - const { - name, - description, - shortDescription, - icon, - category, - features, - pricing, - estimatedTime, - isActive, - featured, - tags - } = req.body; - - // Parse arrays - let featuresArray = []; - let tagsArray = []; - let pricingObj = {}; - - if (features) { - try { - featuresArray = JSON.parse(features); - } catch (e) { - featuresArray = features.split(',').map(f => f.trim()); - } - } - - if (tags) { - try { - tagsArray = JSON.parse(tags); - } catch (e) { - tagsArray = tags.split(',').map(t => t.trim()); - } - } - - if (pricing) { - try { - pricingObj = JSON.parse(pricing); - } catch (e) { - pricingObj = { basePrice: pricing }; - } - } - - const service = await Service.create({ - name, - description, - shortDescription, - icon, - category, - features: featuresArray, - pricing: pricingObj, - estimatedTime, - isActive: isActive === 'on', - featured: featured === 'on', - tags: tagsArray - }); - - res.json({ success: true, service }); - } catch (error) { - console.error('Service creation error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/services/:id', requireAuth, async (req, res) => { - try { - const service = await Service.findByPk(req.params.id); - if (!service) { - return res.status(404).json({ success: false, message: 'Service not found' }); - } - - await service.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Service deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Contacts API Routes -router.patch('/contacts/:id', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - await contact.update(req.body); - res.json({ success: true, contact }); - } catch (error) { - console.error('Contact update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.delete('/contacts/:id', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - await contact.destroy(); - res.json({ success: true }); - } catch (error) { - console.error('Contact deletion error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Telegram notification for contact -router.post('/contacts/:id/telegram', requireAuth, async (req, res) => { - try { - const contact = await Contact.findByPk(req.params.id); - if (!contact) { - return res.status(404).json({ success: false, message: 'Contact not found' }); - } - - // Send Telegram notification - const telegramService = require('../../services/telegram'); - const result = await telegramService.sendContactNotification(contact); - - if (result.success) { - res.json({ success: true }); - } else { - res.status(500).json({ success: false, message: result.message || result.error }); - } - } catch (error) { - console.error('Telegram notification error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -// Test Telegram connection -router.post('/telegram/test', requireAuth, async (req, res) => { - try { - const { botToken, chatId } = req.body; - - // Temporarily set up telegram service with provided credentials - const axios = require('axios'); - - // Test bot info - const botResponse = await axios.get(`https://api.telegram.org/bot${botToken}/getMe`); - - // Test sending a message - const testMessage = '✅ Telegram bot подключен успешно!\n\nЭто тестовое сообщение от SmartSolTech Admin Panel.'; - await axios.post(`https://api.telegram.org/bot${botToken}/sendMessage`, { - chat_id: chatId, - text: testMessage, - parse_mode: 'Markdown' - }); - - res.json({ - success: true, - bot: botResponse.data.result, - message: 'Test message sent successfully' - }); - } catch (error) { - console.error('Telegram test error:', error); - let message = 'Connection failed'; - - if (error.response?.data?.description) { - message = error.response.data.description; - } else if (error.message) { - message = error.message; - } - - res.status(400).json({ success: false, message }); - } -}); - -// Settings API -const { SiteSettings } = require('../../models'); - -router.get('/settings', requireAuth, async (req, res) => { - try { - const settings = await SiteSettings.findOne() || {}; - res.json({ success: true, settings }); - } catch (error) { - console.error('Settings fetch error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -router.post('/settings', requireAuth, upload.fields([ - { name: 'logo', maxCount: 1 }, - { name: 'favicon', maxCount: 1 } -]), async (req, res) => { - try { - let settings = await SiteSettings.findOne(); - if (!settings) { - settings = await SiteSettings.create({}); - } - - const updates = {}; - - // Handle nested objects - Object.keys(req.body).forEach(key => { - if (key.includes('.')) { - const [parent, child] = key.split('.'); - if (!updates[parent]) updates[parent] = {}; - updates[parent][child] = req.body[key]; - } else { - updates[key] = req.body[key]; - } - }); - - // Handle file uploads - if (req.files.logo) { - updates.logo = `/uploads/${req.files.logo[0].filename}`; - } - if (req.files.favicon) { - updates.favicon = `/uploads/${req.files.favicon[0].filename}`; - } - - // Update existing settings with new values - Object.keys(updates).forEach(key => { - if (typeof updates[key] === 'object' && updates[key] !== null) { - settings[key] = { ...settings[key], ...updates[key] }; - } else { - settings[key] = updates[key]; - } - }); - - await settings.save(); - - res.json({ success: true, settings }); - } catch (error) { - console.error('Settings update error:', error); - res.status(500).json({ success: false, message: error.message }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/auth_20251019160707.js b/.history/routes/auth_20251019160707.js deleted file mode 100644 index de4d91e..0000000 --- a/.history/routes/auth_20251019160707.js +++ /dev/null @@ -1,201 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/auth_20251019162544.js b/.history/routes/auth_20251019162544.js deleted file mode 100644 index de4d91e..0000000 --- a/.history/routes/auth_20251019162544.js +++ /dev/null @@ -1,201 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/auth_20251019204627.js b/.history/routes/auth_20251019204627.js deleted file mode 100644 index 7363fcf..0000000 --- a/.history/routes/auth_20251019204627.js +++ /dev/null @@ -1,201 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const jwt = require('jsonwebtoken'); -const { body, validationResult } = require('express-validator'); -const { User } = require('../models'); - -// 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; \ No newline at end of file diff --git a/.history/routes/auth_20251019204633.js b/.history/routes/auth_20251019204633.js deleted file mode 100644 index 790c0b2..0000000 --- a/.history/routes/auth_20251019204633.js +++ /dev/null @@ -1,206 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const jwt = require('jsonwebtoken'); -const { body, validationResult } = require('express-validator'); -const { User } = require('../models'); - -// 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({ - where: { - email: 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; \ No newline at end of file diff --git a/.history/routes/auth_20251019204648.js b/.history/routes/auth_20251019204648.js deleted file mode 100644 index 87df84d..0000000 --- a/.history/routes/auth_20251019204648.js +++ /dev/null @@ -1,207 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const jwt = require('jsonwebtoken'); -const { body, validationResult } = require('express-validator'); -const { User } = require('../models'); - -// 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({ - where: { - email: 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.findByPk(req.session.user.id, { - attributes: { exclude: ['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; \ No newline at end of file diff --git a/.history/routes/auth_20251019204658.js b/.history/routes/auth_20251019204658.js deleted file mode 100644 index 7a71b05..0000000 --- a/.history/routes/auth_20251019204658.js +++ /dev/null @@ -1,207 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const jwt = require('jsonwebtoken'); -const { body, validationResult } = require('express-validator'); -const { User } = require('../models'); - -// 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({ - where: { - email: 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.findByPk(req.session.user.id, { - attributes: { exclude: ['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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/auth_20251019204806.js b/.history/routes/auth_20251019204806.js deleted file mode 100644 index 7a71b05..0000000 --- a/.history/routes/auth_20251019204806.js +++ /dev/null @@ -1,207 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const jwt = require('jsonwebtoken'); -const { body, validationResult } = require('express-validator'); -const { User } = require('../models'); - -// 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({ - where: { - email: 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.findByPk(req.session.user.id, { - attributes: { exclude: ['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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/calculator_20251019160816.js b/.history/routes/calculator_20251019160816.js deleted file mode 100644 index 90559e4..0000000 --- a/.history/routes/calculator_20251019160816.js +++ /dev/null @@ -1,312 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/calculator_20251019162544.js b/.history/routes/calculator_20251019162544.js deleted file mode 100644 index 90559e4..0000000 --- a/.history/routes/calculator_20251019162544.js +++ /dev/null @@ -1,312 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/calculator_20251019204545.js b/.history/routes/calculator_20251019204545.js deleted file mode 100644 index 38732f0..0000000 --- a/.history/routes/calculator_20251019204545.js +++ /dev/null @@ -1,312 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service } = require('../models'); - -// 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; \ No newline at end of file diff --git a/.history/routes/calculator_20251019204551.js b/.history/routes/calculator_20251019204551.js deleted file mode 100644 index cb31511..0000000 --- a/.history/routes/calculator_20251019204551.js +++ /dev/null @@ -1,314 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service } = require('../models'); - -// Get all services for calculator -router.get('/services', async (req, res) => { - try { - const services = await Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/calculator_20251019204602.js b/.history/routes/calculator_20251019204602.js deleted file mode 100644 index d701cda..0000000 --- a/.history/routes/calculator_20251019204602.js +++ /dev/null @@ -1,316 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service } = require('../models'); - -// Get all services for calculator -router.get('/services', async (req, res) => { - try { - const services = await Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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.findAll({ - where: { - id: 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; \ No newline at end of file diff --git a/.history/routes/calculator_20251019204805.js b/.history/routes/calculator_20251019204805.js deleted file mode 100644 index d701cda..0000000 --- a/.history/routes/calculator_20251019204805.js +++ /dev/null @@ -1,316 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service } = require('../models'); - -// Get all services for calculator -router.get('/services', async (req, res) => { - try { - const services = await Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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.findAll({ - where: { - id: 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; \ No newline at end of file diff --git a/.history/routes/contact_20251019160738.js b/.history/routes/contact_20251019160738.js deleted file mode 100644 index 3ffc6e6..0000000 --- a/.history/routes/contact_20251019160738.js +++ /dev/null @@ -1,250 +0,0 @@ -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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251019162544.js b/.history/routes/contact_20251019162544.js deleted file mode 100644 index 3ffc6e6..0000000 --- a/.history/routes/contact_20251019162544.js +++ /dev/null @@ -1,250 +0,0 @@ -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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251019204616.js b/.history/routes/contact_20251019204616.js deleted file mode 100644 index 4bb8161..0000000 --- a/.history/routes/contact_20251019204616.js +++ /dev/null @@ -1,250 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251019204806.js b/.history/routes/contact_20251019204806.js deleted file mode 100644 index 4bb8161..0000000 --- a/.history/routes/contact_20251019204806.js +++ /dev/null @@ -1,250 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251021213241.js b/.history/routes/contact_20251021213241.js deleted file mode 100644 index ef645e4..0000000 --- a/.history/routes/contact_20251021213241.js +++ /dev/null @@ -1,244 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -const telegramService = require('../services/telegram'); - -// 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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251021213250.js b/.history/routes/contact_20251021213250.js deleted file mode 100644 index e44e7f0..0000000 --- a/.history/routes/contact_20251021213250.js +++ /dev/null @@ -1,244 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -const telegramService = require('../services/telegram'); - -// 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 telegramService.sendNewContactAlert(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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251021213258.js b/.history/routes/contact_20251021213258.js deleted file mode 100644 index 53cb066..0000000 --- a/.history/routes/contact_20251021213258.js +++ /dev/null @@ -1,247 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -const telegramService = require('../services/telegram'); - -// 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 telegramService.sendNewContactAlert(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 telegramService.sendCalculatorQuote({ - ...contactData, - services: services.map(s => ({ name: s, price: 0 })) // Simplified for now - }); - - 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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - 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; \ No newline at end of file diff --git a/.history/routes/contact_20251021213310.js b/.history/routes/contact_20251021213310.js deleted file mode 100644 index 6f336eb..0000000 --- a/.history/routes/contact_20251021213310.js +++ /dev/null @@ -1,212 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -const telegramService = require('../services/telegram'); - -// 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 telegramService.sendNewContactAlert(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 telegramService.sendCalculatorQuote({ - ...contactData, - services: services.map(s => ({ name: s, price: 0 })) // Simplified for now - }); - - 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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - await transporter.sendMail(mailOptions); - console.log('Email notification sent successfully'); - } catch (error) { - console.error('Email notification error:', error); - } -} - -// Telegram notifications now handled by telegramService - -// 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; \ No newline at end of file diff --git a/.history/routes/contact_20251021214112.js b/.history/routes/contact_20251021214112.js deleted file mode 100644 index 6f336eb..0000000 --- a/.history/routes/contact_20251021214112.js +++ /dev/null @@ -1,212 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { body, validationResult } = require('express-validator'); -const nodemailer = require('nodemailer'); -const { Contact } = require('../models'); -const telegramService = require('../services/telegram'); - -// 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 telegramService.sendNewContactAlert(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 telegramService.sendCalculatorQuote({ - ...contactData, - services: services.map(s => ({ name: s, price: 0 })) // Simplified for now - }); - - 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: ` -

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.replace(/\n/g, '
')}

-

Source: ${contact.source}

-

Submitted: ${contact.createdAt}

-
-

View in Admin Panel

- ` - }; - - await transporter.sendMail(mailOptions); - console.log('Email notification sent successfully'); - } catch (error) { - console.error('Email notification error:', error); - } -} - -// Telegram notifications now handled by telegramService - -// 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; \ No newline at end of file diff --git a/.history/routes/index_20251019160650.js b/.history/routes/index_20251019160650.js deleted file mode 100644 index 65d2ed0..0000000 --- a/.history/routes/index_20251019160650.js +++ /dev/null @@ -1,203 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/index_20251019162544.js b/.history/routes/index_20251019162544.js deleted file mode 100644 index 65d2ed0..0000000 --- a/.history/routes/index_20251019162544.js +++ /dev/null @@ -1,203 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/index_20251019203116.js b/.history/routes/index_20251019203116.js deleted file mode 100644 index 37ba56c..0000000 --- a/.history/routes/index_20251019203116.js +++ /dev/null @@ -1,201 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); - -// 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; \ No newline at end of file diff --git a/.history/routes/index_20251019203123.js b/.history/routes/index_20251019203123.js deleted file mode 100644 index 7f9a792..0000000 --- a/.history/routes/index_20251019203123.js +++ /dev/null @@ -1,205 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019203147.js b/.history/routes/index_20251019203147.js deleted file mode 100644 index 7f9a792..0000000 --- a/.history/routes/index_20251019203147.js +++ /dev/null @@ -1,205 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019203238.js b/.history/routes/index_20251019203238.js deleted file mode 100644 index c7036b7..0000000 --- a/.history/routes/index_20251019203238.js +++ /dev/null @@ -1,211 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019203244.js b/.history/routes/index_20251019203244.js deleted file mode 100644 index c7dfd6f..0000000 --- a/.history/routes/index_20251019203244.js +++ /dev/null @@ -1,211 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/index_20251019203308.js b/.history/routes/index_20251019203308.js deleted file mode 100644 index 6b71092..0000000 --- a/.history/routes/index_20251019203308.js +++ /dev/null @@ -1,215 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019203315.js b/.history/routes/index_20251019203315.js deleted file mode 100644 index 565f32f..0000000 --- a/.history/routes/index_20251019203315.js +++ /dev/null @@ -1,216 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019203341.js b/.history/routes/index_20251019203341.js deleted file mode 100644 index 565f32f..0000000 --- a/.history/routes/index_20251019203341.js +++ /dev/null @@ -1,216 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019204717.js b/.history/routes/index_20251019204717.js deleted file mode 100644 index e3828a1..0000000 --- a/.history/routes/index_20251019204717.js +++ /dev/null @@ -1,221 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019204728.js b/.history/routes/index_20251019204728.js deleted file mode 100644 index 5126df1..0000000 --- a/.history/routes/index_20251019204728.js +++ /dev/null @@ -1,223 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019204805.js b/.history/routes/index_20251019204805.js deleted file mode 100644 index 5126df1..0000000 --- a/.history/routes/index_20251019204805.js +++ /dev/null @@ -1,223 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019204839.js b/.history/routes/index_20251019204839.js deleted file mode 100644 index 3ee949b..0000000 --- a/.history/routes/index_20251019204839.js +++ /dev/null @@ -1,223 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - portfolioItems: 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251019204914.js b/.history/routes/index_20251019204914.js deleted file mode 100644 index 3ee949b..0000000 --- a/.history/routes/index_20251019204914.js +++ /dev/null @@ -1,223 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - portfolioItems: 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035435.js b/.history/routes/index_20251020035435.js deleted file mode 100644 index 6beb52f..0000000 --- a/.history/routes/index_20251020035435.js +++ /dev/null @@ -1,225 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035447.js b/.history/routes/index_20251020035447.js deleted file mode 100644 index df5cdaf..0000000 --- a/.history/routes/index_20251020035447.js +++ /dev/null @@ -1,230 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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 [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - 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.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - const categories = await Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }); - - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035455.js b/.history/routes/index_20251020035455.js deleted file mode 100644 index 2b21a88..0000000 --- a/.history/routes/index_20251020035455.js +++ /dev/null @@ -1,233 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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 [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - 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 [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - 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.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035505.js b/.history/routes/index_20251020035505.js deleted file mode 100644 index c57e2a6..0000000 --- a/.history/routes/index_20251020035505.js +++ /dev/null @@ -1,237 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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 [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - 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 [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - 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 [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035544.js b/.history/routes/index_20251020035544.js deleted file mode 100644 index 8a6b9a4..0000000 --- a/.history/routes/index_20251020035544.js +++ /dev/null @@ -1,238 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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', - settings: {}, - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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 [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - 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 [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - 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 [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035550.js b/.history/routes/index_20251020035550.js deleted file mode 100644 index b991ee4..0000000 --- a/.history/routes/index_20251020035550.js +++ /dev/null @@ -1,239 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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', - settings: {}, - 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', - settings: {}, - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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 [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - 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 [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - 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 [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035557.js b/.history/routes/index_20251020035557.js deleted file mode 100644 index 21900a4..0000000 --- a/.history/routes/index_20251020035557.js +++ /dev/null @@ -1,240 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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', - settings: {}, - 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', - settings: {}, - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Portfolio detail page -router.get('/portfolio/:id', async (req, res) => { - try { - const [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - 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 [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - 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 [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035603.js b/.history/routes/index_20251020035603.js deleted file mode 100644 index 964220e..0000000 --- a/.history/routes/index_20251020035603.js +++ /dev/null @@ -1,241 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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', - settings: {}, - 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', - settings: {}, - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Portfolio detail page -router.get('/portfolio/:id', async (req, res) => { - try { - const [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); - } catch (error) { - console.error('Portfolio detail error:', error); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Services page -router.get('/services', async (req, res) => { - try { - const [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - 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 [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035610.js b/.history/routes/index_20251020035610.js deleted file mode 100644 index b62bd2f..0000000 --- a/.history/routes/index_20251020035610.js +++ /dev/null @@ -1,242 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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', - settings: {}, - 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', - settings: {}, - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Portfolio detail page -router.get('/portfolio/:id', async (req, res) => { - try { - const [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); - } catch (error) { - console.error('Portfolio detail error:', error); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Services page -router.get('/services', async (req, res) => { - try { - const [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - services, - categories, - currentPage: 'services' - }); - } catch (error) { - console.error('Services page error:', error); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Calculator page -router.get('/calculator', async (req, res) => { - try { - const [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035616.js b/.history/routes/index_20251020035616.js deleted file mode 100644 index de42c39..0000000 --- a/.history/routes/index_20251020035616.js +++ /dev/null @@ -1,243 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio, Service, SiteSettings } = require('../models'); -const { Op } = require('sequelize'); - -// Home page -router.get('/', async (req, res) => { - try { - const [settings, featuredPortfolio, featuredServices] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: { featured: true, isPublished: true }, - order: [['order', 'ASC'], ['createdAt', 'DESC']], - limit: 6 - }), - Service.findAll({ - where: { featured: true, isActive: true }, - order: [['order', 'ASC']], - 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', - settings: {}, - 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', - settings: {}, - 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 [settings, portfolio, total, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findAll({ - where: query, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit - }), - Portfolio.count({ where: query }), - Portfolio.findAll({ - where: { isPublished: true }, - attributes: ['category'], - group: ['category'] - }).then(results => results.map(r => r.category)) - ]); - - const totalPages = Math.ceil(total / limit); - - res.render('portfolio', { - title: 'Portfolio - SmartSolTech', - settings: settings || {}, - portfolioItems: 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', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Portfolio detail page -router.get('/portfolio/:id', async (req, res) => { - try { - const [settings, portfolio] = await Promise.all([ - SiteSettings.findOne() || {}, - Portfolio.findByPk(req.params.id) - ]); - - if (!portfolio || !portfolio.isPublished) { - return res.status(404).render('404', { - title: '404 - Project Not Found', - settings: settings || {}, - message: 'The requested project was not found' - }); - } - - // Increment view count - portfolio.viewCount += 1; - await portfolio.save(); - - // Get related projects - const relatedProjects = await Portfolio.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - order: [['publishedAt', 'DESC']], - limit: 3 - }); - - res.render('portfolio-detail', { - title: `${portfolio.title} - Portfolio - SmartSolTech`, - settings: settings || {}, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); - } catch (error) { - console.error('Portfolio detail error:', error); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Services page -router.get('/services', async (req, res) => { - try { - const [settings, services, categories] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - order: [['featured', 'DESC'], ['order', 'ASC']] - }), - Service.findAll({ - where: { isActive: true }, - attributes: ['category'], - group: ['category'] - }) - ]); - - res.render('services', { - title: 'Services - SmartSolTech', - settings: settings || {}, - services, - categories, - currentPage: 'services' - }); - } catch (error) { - console.error('Services page error:', error); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: 'Something went wrong' - }); - } -}); - -// Calculator page -router.get('/calculator', async (req, res) => { - try { - const [settings, services] = await Promise.all([ - SiteSettings.findOne() || {}, - Service.findAll({ - where: { isActive: true }, - attributes: ['id', 'name', 'pricing', 'category'], - order: [['category', 'ASC'], ['name', 'ASC']] - }) - ]); - - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', - settings: settings || {}, - services, - currentPage: 'calculator' - }); - } catch (error) { - console.error('Calculator page error:', error); - res.status(500).render('error', { - title: 'Error', - settings: {}, - 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; \ No newline at end of file diff --git a/.history/routes/index_20251020035622.js b/.history/routes/index_20251025204913.js similarity index 95% rename from .history/routes/index_20251020035622.js rename to .history/routes/index_20251025204913.js index cd5dfb4..46e9267 100644 --- a/.history/routes/index_20251020035622.js +++ b/.history/routes/index_20251025204913.js @@ -193,7 +193,7 @@ router.get('/services', async (req, res) => { } }); -// Calculator page +// Modern Calculator page (new UX-polished version) router.get('/calculator', async (req, res) => { try { const [settings, services] = await Promise.all([ @@ -205,14 +205,15 @@ router.get('/calculator', async (req, res) => { }) ]); - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', + res.render('calculator-modern', { + title: 'Калькулятор стоимости услуг - SmartSolTech', settings: settings || {}, services, - currentPage: 'calculator' + currentPage: 'calculator', + siteSettings: settings || {} }); } catch (error) { - console.error('Calculator page error:', error); + console.error('Modern calculator page error:', error); res.status(500).render('error', { title: 'Error', settings: {}, diff --git a/.history/routes/index_20251020035856.js b/.history/routes/index_20251025205008.js similarity index 95% rename from .history/routes/index_20251020035856.js rename to .history/routes/index_20251025205008.js index cd5dfb4..46e9267 100644 --- a/.history/routes/index_20251020035856.js +++ b/.history/routes/index_20251025205008.js @@ -193,7 +193,7 @@ router.get('/services', async (req, res) => { } }); -// Calculator page +// Modern Calculator page (new UX-polished version) router.get('/calculator', async (req, res) => { try { const [settings, services] = await Promise.all([ @@ -205,14 +205,15 @@ router.get('/calculator', async (req, res) => { }) ]); - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', + res.render('calculator-modern', { + title: 'Калькулятор стоимости услуг - SmartSolTech', settings: settings || {}, services, - currentPage: 'calculator' + currentPage: 'calculator', + siteSettings: settings || {} }); } catch (error) { - console.error('Calculator page error:', error); + console.error('Modern calculator page error:', error); res.status(500).render('error', { title: 'Error', settings: {}, diff --git a/.history/routes/media_20251019160925.js b/.history/routes/media_20251019160925.js deleted file mode 100644 index d5b7100..0000000 --- a/.history/routes/media_20251019160925.js +++ /dev/null @@ -1,345 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/media_20251019162544.js b/.history/routes/media_20251019162544.js deleted file mode 100644 index d5b7100..0000000 --- a/.history/routes/media_20251019162544.js +++ /dev/null @@ -1,345 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/media_20251022052143.js b/.history/routes/media_20251022052143.js deleted file mode 100644 index fb4c857..0000000 --- a/.history/routes/media_20251022052143.js +++ /dev/null @@ -1,415 +0,0 @@ -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'); - - // Create uploads directory if it doesn't exist - try { - await fs.mkdir(uploadPath, { recursive: true }); - } catch (mkdirError) { - // Directory might already exist, continue - } - - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 50; - - let files; - try { - files = await fs.readdir(uploadPath); - } catch (readdirError) { - if (readdirError.code === 'ENOENT') { - // Directory doesn't exist, return empty list - return res.json({ - success: true, - images: [], - pagination: { - current: 1, - total: 0, - limit, - totalItems: 0, - hasNext: false, - hasPrev: false - } - }); - } - throw readdirError; - } - - const imageFiles = files.filter(file => - /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file) - ); - - // Sort files by modification time (newest first) - const filesWithStats = await Promise.all( - imageFiles.map(async (file) => { - try { - const filePath = path.join(uploadPath, file); - const stats = await fs.stat(filePath); - return { file, stats }; - } catch (error) { - return null; - } - }) - ); - - const validFiles = filesWithStats - .filter(item => item !== null) - .sort((a, b) => b.stats.mtime - a.stats.mtime); - - const total = validFiles.length; - const totalPages = Math.ceil(total / limit); - const start = (page - 1) * limit; - const end = start + limit; - - const paginatedFiles = validFiles.slice(start, end); - - const imagesWithDetails = await Promise.all( - paginatedFiles.map(async ({ file, stats }) => { - try { - const filePath = path.join(uploadPath, file); - - // Try to get image dimensions - let dimensions = null; - try { - const metadata = await sharp(filePath).metadata(); - dimensions = { - width: metadata.width, - height: metadata.height - }; - } catch (sharpError) { - // Not a processable image, skip dimensions - } - - // Determine mime type from extension - const ext = path.extname(file).toLowerCase(); - let mimetype = 'image/jpeg'; - switch (ext) { - case '.png': mimetype = 'image/png'; break; - case '.gif': mimetype = 'image/gif'; break; - case '.webp': mimetype = 'image/webp'; break; - case '.svg': mimetype = 'image/svg+xml'; break; - } - - return { - filename: file, - url: `/uploads/${file}`, - size: stats.size, - mimetype, - uploadedAt: stats.birthtime || stats.mtime, - modifiedAt: stats.mtime, - dimensions, - isImage: true - }; - } catch (error) { - console.error(`Error getting details for ${file}:`, error); - return null; - } - }) - ); - - const validImages = imagesWithDetails.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; \ No newline at end of file diff --git a/.history/routes/media_20251022052247.js b/.history/routes/media_20251022052247.js deleted file mode 100644 index fb4c857..0000000 --- a/.history/routes/media_20251022052247.js +++ /dev/null @@ -1,415 +0,0 @@ -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'); - - // Create uploads directory if it doesn't exist - try { - await fs.mkdir(uploadPath, { recursive: true }); - } catch (mkdirError) { - // Directory might already exist, continue - } - - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 50; - - let files; - try { - files = await fs.readdir(uploadPath); - } catch (readdirError) { - if (readdirError.code === 'ENOENT') { - // Directory doesn't exist, return empty list - return res.json({ - success: true, - images: [], - pagination: { - current: 1, - total: 0, - limit, - totalItems: 0, - hasNext: false, - hasPrev: false - } - }); - } - throw readdirError; - } - - const imageFiles = files.filter(file => - /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file) - ); - - // Sort files by modification time (newest first) - const filesWithStats = await Promise.all( - imageFiles.map(async (file) => { - try { - const filePath = path.join(uploadPath, file); - const stats = await fs.stat(filePath); - return { file, stats }; - } catch (error) { - return null; - } - }) - ); - - const validFiles = filesWithStats - .filter(item => item !== null) - .sort((a, b) => b.stats.mtime - a.stats.mtime); - - const total = validFiles.length; - const totalPages = Math.ceil(total / limit); - const start = (page - 1) * limit; - const end = start + limit; - - const paginatedFiles = validFiles.slice(start, end); - - const imagesWithDetails = await Promise.all( - paginatedFiles.map(async ({ file, stats }) => { - try { - const filePath = path.join(uploadPath, file); - - // Try to get image dimensions - let dimensions = null; - try { - const metadata = await sharp(filePath).metadata(); - dimensions = { - width: metadata.width, - height: metadata.height - }; - } catch (sharpError) { - // Not a processable image, skip dimensions - } - - // Determine mime type from extension - const ext = path.extname(file).toLowerCase(); - let mimetype = 'image/jpeg'; - switch (ext) { - case '.png': mimetype = 'image/png'; break; - case '.gif': mimetype = 'image/gif'; break; - case '.webp': mimetype = 'image/webp'; break; - case '.svg': mimetype = 'image/svg+xml'; break; - } - - return { - filename: file, - url: `/uploads/${file}`, - size: stats.size, - mimetype, - uploadedAt: stats.birthtime || stats.mtime, - modifiedAt: stats.mtime, - dimensions, - isImage: true - }; - } catch (error) { - console.error(`Error getting details for ${file}:`, error); - return null; - } - }) - ); - - const validImages = imagesWithDetails.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; \ No newline at end of file diff --git a/.history/routes/media_20251022195139.js b/.history/routes/media_20251022195139.js deleted file mode 100644 index dd066ec..0000000 --- a/.history/routes/media_20251022195139.js +++ /dev/null @@ -1,733 +0,0 @@ -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 with advanced filtering and search -router.get('/list', requireAuth, async (req, res) => { - try { - const uploadPath = path.join(__dirname, '../public/uploads'); - - // Create uploads directory if it doesn't exist - try { - await fs.mkdir(uploadPath, { recursive: true }); - } catch (mkdirError) { - // Directory might already exist, continue - } - - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 24; - const search = req.query.search?.toLowerCase() || ''; - const sortBy = req.query.sortBy || 'date'; // date, name, size - const sortOrder = req.query.sortOrder || 'desc'; // asc, desc - const fileType = req.query.fileType || 'all'; // all, image, video, document - - let files; - try { - files = await fs.readdir(uploadPath); - } catch (readdirError) { - if (readdirError.code === 'ENOENT') { - return res.json({ - success: true, - images: [], - pagination: { - current: 1, - total: 0, - limit, - totalItems: 0, - hasNext: false, - hasPrev: false - }, - filters: { - search: '', - sortBy: 'date', - sortOrder: 'desc', - fileType: 'all' - } - }); - } - throw readdirError; - } - - // Filter by file type - let filteredFiles = files; - if (fileType !== 'all') { - const typePatterns = { - image: /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i, - video: /\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i, - document: /\.(pdf|doc|docx|txt|rtf|odt)$/i - }; - - const pattern = typePatterns[fileType]; - if (pattern) { - filteredFiles = files.filter(file => pattern.test(file)); - } - } else { - // Only show supported media files - filteredFiles = files.filter(file => - /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?|mp4|webm|avi|mov|mkv|pdf|doc|docx)$/i.test(file) - ); - } - - // Apply search filter - if (search) { - filteredFiles = filteredFiles.filter(file => - file.toLowerCase().includes(search) - ); - } - - // Get file stats and create file objects - const filesWithStats = await Promise.all( - filteredFiles.map(async (file) => { - try { - const filePath = path.join(uploadPath, file); - const stats = await fs.stat(filePath); - return { file, stats, filePath }; - } catch (error) { - return null; - } - }) - ); - - const validFiles = filesWithStats.filter(item => item !== null); - - // Sort files - validFiles.sort((a, b) => { - let aValue, bValue; - - switch (sortBy) { - case 'name': - aValue = a.file.toLowerCase(); - bValue = b.file.toLowerCase(); - break; - case 'size': - aValue = a.stats.size; - bValue = b.stats.size; - break; - case 'date': - default: - aValue = a.stats.mtime; - bValue = b.stats.mtime; - break; - } - - if (sortOrder === 'asc') { - return aValue > bValue ? 1 : -1; - } else { - return aValue < bValue ? 1 : -1; - } - }); - - const total = validFiles.length; - const totalPages = Math.ceil(total / limit); - const start = (page - 1) * limit; - const end = start + limit; - - const paginatedFiles = validFiles.slice(start, end); - - const filesWithDetails = await Promise.all( - paginatedFiles.map(async ({ file, stats, filePath }) => { - try { - const ext = path.extname(file).toLowerCase(); - let fileDetails = { - filename: file, - url: `/uploads/${file}`, - size: stats.size, - uploadedAt: stats.birthtime || stats.mtime, - modifiedAt: stats.mtime, - extension: ext, - isImage: false, - isVideo: false, - isDocument: false - }; - - // Determine file type and get additional info - if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(file)) { - fileDetails.isImage = true; - fileDetails.mimetype = `image/${ext.replace('.', '')}`; - - // Get image dimensions - try { - const metadata = await sharp(filePath).metadata(); - fileDetails.dimensions = { - width: metadata.width, - height: metadata.height - }; - fileDetails.format = metadata.format; - } catch (sharpError) { - console.warn(`Could not get image metadata for ${file}`); - } - } else if (/\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i.test(file)) { - fileDetails.isVideo = true; - fileDetails.mimetype = `video/${ext.replace('.', '')}`; - } else if (/\.(pdf|doc|docx|txt|rtf|odt)$/i.test(file)) { - fileDetails.isDocument = true; - fileDetails.mimetype = `application/${ext.replace('.', '')}`; - } - - // Generate thumbnail for images - if (fileDetails.isImage && !file.includes('-thumbnail.')) { - const thumbnailPath = path.join(uploadPath, `${path.parse(file).name}-thumbnail.webp`); - try { - await fs.access(thumbnailPath); - fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`; - } catch { - // Thumbnail doesn't exist, create it - try { - await sharp(filePath) - .resize(200, 150, { - fit: 'cover', - withoutEnlargement: false - }) - .webp({ quality: 80 }) - .toFile(thumbnailPath); - fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`; - } catch (thumbError) { - console.warn(`Could not create thumbnail for ${file}`); - } - } - } - - return fileDetails; - } catch (error) { - console.error(`Error getting details for ${file}:`, error); - return null; - } - }) - ); - - const validMedia = filesWithDetails.filter(item => item !== null); - - // Calculate storage stats - const totalSize = validFiles.reduce((sum, file) => sum + file.stats.size, 0); - const imageCount = validMedia.filter(f => f.isImage).length; - const videoCount = validMedia.filter(f => f.isVideo).length; - const documentCount = validMedia.filter(f => f.isDocument).length; - - res.json({ - success: true, - files: validMedia, - pagination: { - current: page, - total: totalPages, - limit, - totalItems: total, - hasNext: page < totalPages, - hasPrev: page > 1 - }, - filters: { - search, - sortBy, - sortOrder, - fileType - }, - stats: { - totalFiles: total, - totalSize, - imageCount, - videoCount, - documentCount, - formattedSize: formatFileSize(totalSize) - } - }); - } catch (error) { - console.error('List media error:', error); - res.status(500).json({ - success: false, - message: 'Error listing media files' - }); - } -}); - -// Create folder structure -router.post('/folder', requireAuth, async (req, res) => { - try { - const { folderName } = req.body; - - if (!folderName || !folderName.trim()) { - return res.status(400).json({ - success: false, - message: 'Folder name is required' - }); - } - - // Sanitize folder name - const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_]/g, '-'); - const folderPath = path.join(__dirname, '../public/uploads', sanitizedName); - - try { - await fs.mkdir(folderPath, { recursive: true }); - - res.json({ - success: true, - message: 'Folder created successfully', - folderName: sanitizedName, - folderPath: `/uploads/${sanitizedName}` - }); - } catch (error) { - if (error.code === 'EEXIST') { - return res.status(400).json({ - success: false, - message: 'Folder already exists' - }); - } - throw error; - } - } catch (error) { - console.error('Create folder error:', error); - res.status(500).json({ - success: false, - message: 'Error creating folder' - }); - } -}); - -// Get media file info -router.get('/info/:filename', requireAuth, async (req, res) => { - try { - const filename = req.params.filename; - - // Security check - if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { - return res.status(400).json({ - success: false, - message: 'Invalid filename' - }); - } - - const filePath = path.join(__dirname, '../public/uploads', filename); - - try { - const stats = await fs.stat(filePath); - const ext = path.extname(filename).toLowerCase(); - - let fileInfo = { - filename, - url: `/uploads/${filename}`, - size: stats.size, - formattedSize: formatFileSize(stats.size), - uploadedAt: stats.birthtime || stats.mtime, - modifiedAt: stats.mtime, - extension: ext, - mimetype: getMimeType(ext) - }; - - // Get additional info for images - if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(filename)) { - try { - const metadata = await sharp(filePath).metadata(); - fileInfo.dimensions = { - width: metadata.width, - height: metadata.height - }; - fileInfo.format = metadata.format; - fileInfo.hasAlpha = metadata.hasAlpha; - fileInfo.density = metadata.density; - } catch (sharpError) { - console.warn(`Could not get image metadata for ${filename}`); - } - } - - res.json({ - success: true, - fileInfo - }); - } catch (error) { - if (error.code === 'ENOENT') { - return res.status(404).json({ - success: false, - message: 'File not found' - }); - } - throw error; - } - } catch (error) { - console.error('Get file info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting file information' - }); - } -}); - -// Resize image -router.post('/resize/:filename', requireAuth, async (req, res) => { - try { - const filename = req.params.filename; - const { width, height, quality = 85 } = req.body; - - if (!width && !height) { - return res.status(400).json({ - success: false, - message: 'Width or height must be specified' - }); - } - - // Security check - if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { - return res.status(400).json({ - success: false, - message: 'Invalid filename' - }); - } - - const originalPath = path.join(__dirname, '../public/uploads', filename); - const nameWithoutExt = path.parse(filename).name; - const resizedPath = path.join( - path.dirname(originalPath), - `${nameWithoutExt}-${width || 'auto'}x${height || 'auto'}.webp` - ); - - try { - let sharpInstance = sharp(originalPath); - - if (width && height) { - sharpInstance = sharpInstance.resize(parseInt(width), parseInt(height), { - fit: 'cover' - }); - } else if (width) { - sharpInstance = sharpInstance.resize(parseInt(width)); - } else { - sharpInstance = sharpInstance.resize(null, parseInt(height)); - } - - await sharpInstance - .webp({ quality: parseInt(quality) }) - .toFile(resizedPath); - - res.json({ - success: true, - message: 'Image resized successfully', - originalFile: filename, - resizedFile: path.basename(resizedPath), - resizedUrl: `/uploads/${path.basename(resizedPath)}` - }); - } catch (error) { - if (error.code === 'ENOENT') { - return res.status(404).json({ - success: false, - message: 'Original file not found' - }); - } - throw error; - } - } catch (error) { - console.error('Resize image error:', error); - res.status(500).json({ - success: false, - message: 'Error resizing image' - }); - } -}); - -// Utility functions -function formatFileSize(bytes) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -function getMimeType(ext) { - const mimeTypes = { - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', - '.webp': 'image/webp', - '.svg': 'image/svg+xml', - '.bmp': 'image/bmp', - '.tiff': 'image/tiff', - '.tif': 'image/tiff', - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.avi': 'video/x-msvideo', - '.mov': 'video/quicktime', - '.mkv': 'video/x-matroska', - '.pdf': 'application/pdf', - '.doc': 'application/msword', - '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - }; - - return mimeTypes[ext.toLowerCase()] || 'application/octet-stream'; -} - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/media_20251022195905.js b/.history/routes/media_20251022195905.js deleted file mode 100644 index dd066ec..0000000 --- a/.history/routes/media_20251022195905.js +++ /dev/null @@ -1,733 +0,0 @@ -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 with advanced filtering and search -router.get('/list', requireAuth, async (req, res) => { - try { - const uploadPath = path.join(__dirname, '../public/uploads'); - - // Create uploads directory if it doesn't exist - try { - await fs.mkdir(uploadPath, { recursive: true }); - } catch (mkdirError) { - // Directory might already exist, continue - } - - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 24; - const search = req.query.search?.toLowerCase() || ''; - const sortBy = req.query.sortBy || 'date'; // date, name, size - const sortOrder = req.query.sortOrder || 'desc'; // asc, desc - const fileType = req.query.fileType || 'all'; // all, image, video, document - - let files; - try { - files = await fs.readdir(uploadPath); - } catch (readdirError) { - if (readdirError.code === 'ENOENT') { - return res.json({ - success: true, - images: [], - pagination: { - current: 1, - total: 0, - limit, - totalItems: 0, - hasNext: false, - hasPrev: false - }, - filters: { - search: '', - sortBy: 'date', - sortOrder: 'desc', - fileType: 'all' - } - }); - } - throw readdirError; - } - - // Filter by file type - let filteredFiles = files; - if (fileType !== 'all') { - const typePatterns = { - image: /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i, - video: /\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i, - document: /\.(pdf|doc|docx|txt|rtf|odt)$/i - }; - - const pattern = typePatterns[fileType]; - if (pattern) { - filteredFiles = files.filter(file => pattern.test(file)); - } - } else { - // Only show supported media files - filteredFiles = files.filter(file => - /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?|mp4|webm|avi|mov|mkv|pdf|doc|docx)$/i.test(file) - ); - } - - // Apply search filter - if (search) { - filteredFiles = filteredFiles.filter(file => - file.toLowerCase().includes(search) - ); - } - - // Get file stats and create file objects - const filesWithStats = await Promise.all( - filteredFiles.map(async (file) => { - try { - const filePath = path.join(uploadPath, file); - const stats = await fs.stat(filePath); - return { file, stats, filePath }; - } catch (error) { - return null; - } - }) - ); - - const validFiles = filesWithStats.filter(item => item !== null); - - // Sort files - validFiles.sort((a, b) => { - let aValue, bValue; - - switch (sortBy) { - case 'name': - aValue = a.file.toLowerCase(); - bValue = b.file.toLowerCase(); - break; - case 'size': - aValue = a.stats.size; - bValue = b.stats.size; - break; - case 'date': - default: - aValue = a.stats.mtime; - bValue = b.stats.mtime; - break; - } - - if (sortOrder === 'asc') { - return aValue > bValue ? 1 : -1; - } else { - return aValue < bValue ? 1 : -1; - } - }); - - const total = validFiles.length; - const totalPages = Math.ceil(total / limit); - const start = (page - 1) * limit; - const end = start + limit; - - const paginatedFiles = validFiles.slice(start, end); - - const filesWithDetails = await Promise.all( - paginatedFiles.map(async ({ file, stats, filePath }) => { - try { - const ext = path.extname(file).toLowerCase(); - let fileDetails = { - filename: file, - url: `/uploads/${file}`, - size: stats.size, - uploadedAt: stats.birthtime || stats.mtime, - modifiedAt: stats.mtime, - extension: ext, - isImage: false, - isVideo: false, - isDocument: false - }; - - // Determine file type and get additional info - if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(file)) { - fileDetails.isImage = true; - fileDetails.mimetype = `image/${ext.replace('.', '')}`; - - // Get image dimensions - try { - const metadata = await sharp(filePath).metadata(); - fileDetails.dimensions = { - width: metadata.width, - height: metadata.height - }; - fileDetails.format = metadata.format; - } catch (sharpError) { - console.warn(`Could not get image metadata for ${file}`); - } - } else if (/\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i.test(file)) { - fileDetails.isVideo = true; - fileDetails.mimetype = `video/${ext.replace('.', '')}`; - } else if (/\.(pdf|doc|docx|txt|rtf|odt)$/i.test(file)) { - fileDetails.isDocument = true; - fileDetails.mimetype = `application/${ext.replace('.', '')}`; - } - - // Generate thumbnail for images - if (fileDetails.isImage && !file.includes('-thumbnail.')) { - const thumbnailPath = path.join(uploadPath, `${path.parse(file).name}-thumbnail.webp`); - try { - await fs.access(thumbnailPath); - fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`; - } catch { - // Thumbnail doesn't exist, create it - try { - await sharp(filePath) - .resize(200, 150, { - fit: 'cover', - withoutEnlargement: false - }) - .webp({ quality: 80 }) - .toFile(thumbnailPath); - fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`; - } catch (thumbError) { - console.warn(`Could not create thumbnail for ${file}`); - } - } - } - - return fileDetails; - } catch (error) { - console.error(`Error getting details for ${file}:`, error); - return null; - } - }) - ); - - const validMedia = filesWithDetails.filter(item => item !== null); - - // Calculate storage stats - const totalSize = validFiles.reduce((sum, file) => sum + file.stats.size, 0); - const imageCount = validMedia.filter(f => f.isImage).length; - const videoCount = validMedia.filter(f => f.isVideo).length; - const documentCount = validMedia.filter(f => f.isDocument).length; - - res.json({ - success: true, - files: validMedia, - pagination: { - current: page, - total: totalPages, - limit, - totalItems: total, - hasNext: page < totalPages, - hasPrev: page > 1 - }, - filters: { - search, - sortBy, - sortOrder, - fileType - }, - stats: { - totalFiles: total, - totalSize, - imageCount, - videoCount, - documentCount, - formattedSize: formatFileSize(totalSize) - } - }); - } catch (error) { - console.error('List media error:', error); - res.status(500).json({ - success: false, - message: 'Error listing media files' - }); - } -}); - -// Create folder structure -router.post('/folder', requireAuth, async (req, res) => { - try { - const { folderName } = req.body; - - if (!folderName || !folderName.trim()) { - return res.status(400).json({ - success: false, - message: 'Folder name is required' - }); - } - - // Sanitize folder name - const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_]/g, '-'); - const folderPath = path.join(__dirname, '../public/uploads', sanitizedName); - - try { - await fs.mkdir(folderPath, { recursive: true }); - - res.json({ - success: true, - message: 'Folder created successfully', - folderName: sanitizedName, - folderPath: `/uploads/${sanitizedName}` - }); - } catch (error) { - if (error.code === 'EEXIST') { - return res.status(400).json({ - success: false, - message: 'Folder already exists' - }); - } - throw error; - } - } catch (error) { - console.error('Create folder error:', error); - res.status(500).json({ - success: false, - message: 'Error creating folder' - }); - } -}); - -// Get media file info -router.get('/info/:filename', requireAuth, async (req, res) => { - try { - const filename = req.params.filename; - - // Security check - if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { - return res.status(400).json({ - success: false, - message: 'Invalid filename' - }); - } - - const filePath = path.join(__dirname, '../public/uploads', filename); - - try { - const stats = await fs.stat(filePath); - const ext = path.extname(filename).toLowerCase(); - - let fileInfo = { - filename, - url: `/uploads/${filename}`, - size: stats.size, - formattedSize: formatFileSize(stats.size), - uploadedAt: stats.birthtime || stats.mtime, - modifiedAt: stats.mtime, - extension: ext, - mimetype: getMimeType(ext) - }; - - // Get additional info for images - if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(filename)) { - try { - const metadata = await sharp(filePath).metadata(); - fileInfo.dimensions = { - width: metadata.width, - height: metadata.height - }; - fileInfo.format = metadata.format; - fileInfo.hasAlpha = metadata.hasAlpha; - fileInfo.density = metadata.density; - } catch (sharpError) { - console.warn(`Could not get image metadata for ${filename}`); - } - } - - res.json({ - success: true, - fileInfo - }); - } catch (error) { - if (error.code === 'ENOENT') { - return res.status(404).json({ - success: false, - message: 'File not found' - }); - } - throw error; - } - } catch (error) { - console.error('Get file info error:', error); - res.status(500).json({ - success: false, - message: 'Error getting file information' - }); - } -}); - -// Resize image -router.post('/resize/:filename', requireAuth, async (req, res) => { - try { - const filename = req.params.filename; - const { width, height, quality = 85 } = req.body; - - if (!width && !height) { - return res.status(400).json({ - success: false, - message: 'Width or height must be specified' - }); - } - - // Security check - if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { - return res.status(400).json({ - success: false, - message: 'Invalid filename' - }); - } - - const originalPath = path.join(__dirname, '../public/uploads', filename); - const nameWithoutExt = path.parse(filename).name; - const resizedPath = path.join( - path.dirname(originalPath), - `${nameWithoutExt}-${width || 'auto'}x${height || 'auto'}.webp` - ); - - try { - let sharpInstance = sharp(originalPath); - - if (width && height) { - sharpInstance = sharpInstance.resize(parseInt(width), parseInt(height), { - fit: 'cover' - }); - } else if (width) { - sharpInstance = sharpInstance.resize(parseInt(width)); - } else { - sharpInstance = sharpInstance.resize(null, parseInt(height)); - } - - await sharpInstance - .webp({ quality: parseInt(quality) }) - .toFile(resizedPath); - - res.json({ - success: true, - message: 'Image resized successfully', - originalFile: filename, - resizedFile: path.basename(resizedPath), - resizedUrl: `/uploads/${path.basename(resizedPath)}` - }); - } catch (error) { - if (error.code === 'ENOENT') { - return res.status(404).json({ - success: false, - message: 'Original file not found' - }); - } - throw error; - } - } catch (error) { - console.error('Resize image error:', error); - res.status(500).json({ - success: false, - message: 'Error resizing image' - }); - } -}); - -// Utility functions -function formatFileSize(bytes) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -function getMimeType(ext) { - const mimeTypes = { - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', - '.webp': 'image/webp', - '.svg': 'image/svg+xml', - '.bmp': 'image/bmp', - '.tiff': 'image/tiff', - '.tif': 'image/tiff', - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.avi': 'video/x-msvideo', - '.mov': 'video/quicktime', - '.mkv': 'video/x-matroska', - '.pdf': 'application/pdf', - '.doc': 'application/msword', - '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - }; - - return mimeTypes[ext.toLowerCase()] || 'application/octet-stream'; -} - -module.exports = router; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019160838.js b/.history/routes/portfolio_20251019160838.js deleted file mode 100644 index 0164537..0000000 --- a/.history/routes/portfolio_20251019160838.js +++ /dev/null @@ -1,196 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019162544.js b/.history/routes/portfolio_20251019162544.js deleted file mode 100644 index 0164537..0000000 --- a/.history/routes/portfolio_20251019162544.js +++ /dev/null @@ -1,196 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019203104.js b/.history/routes/portfolio_20251019203104.js deleted file mode 100644 index c86551b..0000000 --- a/.history/routes/portfolio_20251019203104.js +++ /dev/null @@ -1,197 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019203147.js b/.history/routes/portfolio_20251019203147.js deleted file mode 100644 index c86551b..0000000 --- a/.history/routes/portfolio_20251019203147.js +++ /dev/null @@ -1,197 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019204338.js b/.history/routes/portfolio_20251019204338.js deleted file mode 100644 index 8000d4d..0000000 --- a/.history/routes/portfolio_20251019204338.js +++ /dev/null @@ -1,206 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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 whereClause = { isPublished: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - if (search) { - whereClause = { - ...whereClause, - [Op.or]: [ - { title: { [Op.iLike]: `%${search}%` } }, - { description: { [Op.iLike]: `%${search}%` } }, - { shortDescription: { [Op.iLike]: `%${search}%` } } - ] - }; - } - - // Get portfolio items - const [portfolio, total] = await Promise.all([ - Portfolio.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit, - attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount'] - }), - Portfolio.count({ where: whereClause }) - ]); - - 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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019204352.js b/.history/routes/portfolio_20251019204352.js deleted file mode 100644 index 44dba44..0000000 --- a/.history/routes/portfolio_20251019204352.js +++ /dev/null @@ -1,206 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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 whereClause = { isPublished: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - if (search) { - whereClause = { - ...whereClause, - [Op.or]: [ - { title: { [Op.iLike]: `%${search}%` } }, - { description: { [Op.iLike]: `%${search}%` } }, - { shortDescription: { [Op.iLike]: `%${search}%` } } - ] - }; - } - - // Get portfolio items - const [portfolio, total] = await Promise.all([ - Portfolio.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit, - attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount'] - }), - Portfolio.count({ where: whereClause }) - ]); - - 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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019204403.js b/.history/routes/portfolio_20251019204403.js deleted file mode 100644 index cfde0d6..0000000 --- a/.history/routes/portfolio_20251019204403.js +++ /dev/null @@ -1,208 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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 whereClause = { isPublished: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - if (search) { - whereClause = { - ...whereClause, - [Op.or]: [ - { title: { [Op.iLike]: `%${search}%` } }, - { description: { [Op.iLike]: `%${search}%` } }, - { shortDescription: { [Op.iLike]: `%${search}%` } } - ] - }; - } - - // Get portfolio items - const [portfolio, total] = await Promise.all([ - Portfolio.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit, - attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount'] - }), - Portfolio.count({ where: whereClause }) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - attributes: ['id', 'title', 'shortDescription', 'images'], - limit: 3 - }); - - 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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019204418.js b/.history/routes/portfolio_20251019204418.js deleted file mode 100644 index 504cfa4..0000000 --- a/.history/routes/portfolio_20251019204418.js +++ /dev/null @@ -1,208 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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 whereClause = { isPublished: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - if (search) { - whereClause = { - ...whereClause, - [Op.or]: [ - { title: { [Op.iLike]: `%${search}%` } }, - { description: { [Op.iLike]: `%${search}%` } }, - { shortDescription: { [Op.iLike]: `%${search}%` } } - ] - }; - } - - // Get portfolio items - const [portfolio, total] = await Promise.all([ - Portfolio.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit, - attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount'] - }), - Portfolio.count({ where: whereClause }) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - attributes: ['id', 'title', 'shortDescription', 'images'], - limit: 3 - }); - - 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.findByPk(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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019204435.js b/.history/routes/portfolio_20251019204435.js deleted file mode 100644 index 7c637ae..0000000 --- a/.history/routes/portfolio_20251019204435.js +++ /dev/null @@ -1,209 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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 whereClause = { isPublished: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - if (search) { - whereClause = { - ...whereClause, - [Op.or]: [ - { title: { [Op.iLike]: `%${search}%` } }, - { description: { [Op.iLike]: `%${search}%` } }, - { shortDescription: { [Op.iLike]: `%${search}%` } } - ] - }; - } - - // Get portfolio items - const [portfolio, total] = await Promise.all([ - Portfolio.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit, - attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount'] - }), - Portfolio.count({ where: whereClause }) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - attributes: ['id', 'title', 'shortDescription', 'images'], - limit: 3 - }); - - 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.findByPk(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.findAll({ - where: { - [Op.and]: [ - { isPublished: true }, - { - [Op.or]: [ - { title: { [Op.iLike]: `%${searchTerm}%` } }, - { description: { [Op.iLike]: `%${searchTerm}%` } }, - { technologies: { [Op.contains]: [searchTerm] } } - ] - } - ] - }, - attributes: ['id', 'title', 'shortDescription', 'images', 'category'], - 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; \ No newline at end of file diff --git a/.history/routes/portfolio_20251019204805.js b/.history/routes/portfolio_20251019204805.js deleted file mode 100644 index 7c637ae..0000000 --- a/.history/routes/portfolio_20251019204805.js +++ /dev/null @@ -1,209 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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 whereClause = { isPublished: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - if (search) { - whereClause = { - ...whereClause, - [Op.or]: [ - { title: { [Op.iLike]: `%${search}%` } }, - { description: { [Op.iLike]: `%${search}%` } }, - { shortDescription: { [Op.iLike]: `%${search}%` } } - ] - }; - } - - // Get portfolio items - const [portfolio, total] = await Promise.all([ - Portfolio.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['publishedAt', 'DESC']], - offset: skip, - limit: limit, - attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount'] - }), - Portfolio.count({ where: whereClause }) - ]); - - 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.findByPk(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.findAll({ - where: { - id: { [Op.ne]: portfolio.id }, - category: portfolio.category, - isPublished: true - }, - attributes: ['id', 'title', 'shortDescription', 'images'], - limit: 3 - }); - - 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.findByPk(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.findAll({ - where: { - [Op.and]: [ - { isPublished: true }, - { - [Op.or]: [ - { title: { [Op.iLike]: `%${searchTerm}%` } }, - { description: { [Op.iLike]: `%${searchTerm}%` } }, - { technologies: { [Op.contains]: [searchTerm] } } - ] - } - ] - }, - attributes: ['id', 'title', 'shortDescription', 'images', 'category'], - 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; \ No newline at end of file diff --git a/.history/routes/services_20251019160852.js b/.history/routes/services_20251019160852.js deleted file mode 100644 index d6ae97c..0000000 --- a/.history/routes/services_20251019160852.js +++ /dev/null @@ -1,141 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/services_20251019162544.js b/.history/routes/services_20251019162544.js deleted file mode 100644 index d6ae97c..0000000 --- a/.history/routes/services_20251019162544.js +++ /dev/null @@ -1,141 +0,0 @@ -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; \ No newline at end of file diff --git a/.history/routes/services_20251019204450.js b/.history/routes/services_20251019204450.js deleted file mode 100644 index 3e3221c..0000000 --- a/.history/routes/services_20251019204450.js +++ /dev/null @@ -1,141 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service, Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// 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; \ No newline at end of file diff --git a/.history/routes/services_20251019204458.js b/.history/routes/services_20251019204458.js deleted file mode 100644 index da8d2cc..0000000 --- a/.history/routes/services_20251019204458.js +++ /dev/null @@ -1,142 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service, Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// Get all services -router.get('/', async (req, res) => { - try { - const category = req.query.category; - const featured = req.query.featured; - - let whereClause = { isActive: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - const services = await Service.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - 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; \ No newline at end of file diff --git a/.history/routes/services_20251019204511.js b/.history/routes/services_20251019204511.js deleted file mode 100644 index 4e5bd09..0000000 --- a/.history/routes/services_20251019204511.js +++ /dev/null @@ -1,143 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service, Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// Get all services -router.get('/', async (req, res) => { - try { - const category = req.query.category; - const featured = req.query.featured; - - let whereClause = { isActive: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - const services = await Service.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - 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.findByPk(req.params.id); - - if (!service || !service.isActive) { - return res.status(404).json({ - success: false, - message: 'Service not found' - }); - } - - // Get related services - const relatedServices = await Service.findAll({ - where: { - id: { [Op.ne]: service.id }, - category: service.category, - isActive: true - }, - limit: 3 - }); - .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; \ No newline at end of file diff --git a/.history/routes/services_20251019204521.js b/.history/routes/services_20251019204521.js deleted file mode 100644 index 441b1d2..0000000 --- a/.history/routes/services_20251019204521.js +++ /dev/null @@ -1,142 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service, Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// Get all services -router.get('/', async (req, res) => { - try { - const category = req.query.category; - const featured = req.query.featured; - - let whereClause = { isActive: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - const services = await Service.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - 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.findByPk(req.params.id); - - if (!service || !service.isActive) { - return res.status(404).json({ - success: false, - message: 'Service not found' - }); - } - - // Get related services - const relatedServices = await Service.findAll({ - where: { - id: { [Op.ne]: service.id }, - category: service.category, - isActive: true - }, - 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; \ No newline at end of file diff --git a/.history/routes/services_20251019204534.js b/.history/routes/services_20251019204534.js deleted file mode 100644 index 601fa05..0000000 --- a/.history/routes/services_20251019204534.js +++ /dev/null @@ -1,142 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service, Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// Get all services -router.get('/', async (req, res) => { - try { - const category = req.query.category; - const featured = req.query.featured; - - let whereClause = { isActive: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - const services = await Service.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - 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.findByPk(req.params.id); - - if (!service || !service.isActive) { - return res.status(404).json({ - success: false, - message: 'Service not found' - }); - } - - // Get related services - const relatedServices = await Service.findAll({ - where: { - id: { [Op.ne]: service.id }, - category: service.category, - isActive: true - }, - 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.findAll({ - where: { - [Op.and]: [ - { isActive: true }, - { - [Op.or]: [ - { name: { [Op.iLike]: `%${searchTerm}%` } }, - { description: { [Op.iLike]: `%${searchTerm}%` } }, - { tags: { [Op.contains]: [searchTerm] } } - ] - } - ] - }, - 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; \ No newline at end of file diff --git a/.history/routes/services_20251019204805.js b/.history/routes/services_20251019204805.js deleted file mode 100644 index 601fa05..0000000 --- a/.history/routes/services_20251019204805.js +++ /dev/null @@ -1,142 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Service, Portfolio } = require('../models'); -const { Op } = require('sequelize'); - -// Get all services -router.get('/', async (req, res) => { - try { - const category = req.query.category; - const featured = req.query.featured; - - let whereClause = { isActive: true }; - - if (category && category !== 'all') { - whereClause.category = category; - } - - if (featured === 'true') { - whereClause.featured = true; - } - - const services = await Service.findAll({ - where: whereClause, - order: [['featured', 'DESC'], ['order', 'ASC']] - }); - - 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.findByPk(req.params.id); - - if (!service || !service.isActive) { - return res.status(404).json({ - success: false, - message: 'Service not found' - }); - } - - // Get related services - const relatedServices = await Service.findAll({ - where: { - id: { [Op.ne]: service.id }, - category: service.category, - isActive: true - }, - 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.findAll({ - where: { - [Op.and]: [ - { isActive: true }, - { - [Op.or]: [ - { name: { [Op.iLike]: `%${searchTerm}%` } }, - { description: { [Op.iLike]: `%${searchTerm}%` } }, - { tags: { [Op.contains]: [searchTerm] } } - ] - } - ] - }, - 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; \ No newline at end of file diff --git a/.history/scripts/build_20251019162023.js b/.history/scripts/build_20251019162023.js deleted file mode 100644 index 2b54f55..0000000 --- a/.history/scripts/build_20251019162023.js +++ /dev/null @@ -1,279 +0,0 @@ -#!/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 }; \ No newline at end of file diff --git a/.history/scripts/build_20251019162545.js b/.history/scripts/build_20251019162545.js deleted file mode 100644 index 2b54f55..0000000 --- a/.history/scripts/build_20251019162545.js +++ /dev/null @@ -1,279 +0,0 @@ -#!/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 }; \ No newline at end of file diff --git a/.history/scripts/dev_20251019161952.js b/.history/scripts/dev_20251019161952.js deleted file mode 100644 index 0a9d859..0000000 --- a/.history/scripts/dev_20251019161952.js +++ /dev/null @@ -1,73 +0,0 @@ -#!/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 }; \ No newline at end of file diff --git a/.history/scripts/dev_20251019162545.js b/.history/scripts/dev_20251019162545.js deleted file mode 100644 index 0a9d859..0000000 --- a/.history/scripts/dev_20251019162545.js +++ /dev/null @@ -1,73 +0,0 @@ -#!/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 }; \ No newline at end of file diff --git a/.history/scripts/dev_20251019203008.js b/.history/scripts/dev_20251019203008.js deleted file mode 100644 index 3c1a5fc..0000000 --- a/.history/scripts/dev_20251019203008.js +++ /dev/null @@ -1,73 +0,0 @@ -#!/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 = [ - 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 }; \ No newline at end of file diff --git a/.history/scripts/dev_20251019203023.js b/.history/scripts/dev_20251019203023.js deleted file mode 100644 index 3c1a5fc..0000000 --- a/.history/scripts/dev_20251019203023.js +++ /dev/null @@ -1,73 +0,0 @@ -#!/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 = [ - 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 }; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019161840.js b/.history/scripts/init-db_20251019161840.js deleted file mode 100644 index 5c4d272..0000000 --- a/.history/scripts/init-db_20251019161840.js +++ /dev/null @@ -1,498 +0,0 @@ -#!/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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019162545.js b/.history/scripts/init-db_20251019162545.js deleted file mode 100644 index 5c4d272..0000000 --- a/.history/scripts/init-db_20251019162545.js +++ /dev/null @@ -1,498 +0,0 @@ -#!/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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202228.js b/.history/scripts/init-db_20251019202228.js deleted file mode 100644 index 6118a9d..0000000 --- a/.history/scripts/init-db_20251019202228.js +++ /dev/null @@ -1,495 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202238.js b/.history/scripts/init-db_20251019202238.js deleted file mode 100644 index 40ef93d..0000000 --- a/.history/scripts/init-db_20251019202238.js +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202245.js b/.history/scripts/init-db_20251019202245.js deleted file mode 100644 index f08fb5a..0000000 --- a/.history/scripts/init-db_20251019202245.js +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202251.js b/.history/scripts/init-db_20251019202251.js deleted file mode 100644 index a21c857..0000000 --- a/.history/scripts/init-db_20251019202251.js +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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.bulkCreate(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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202258.js b/.history/scripts/init-db_20251019202258.js deleted file mode 100644 index bd874b5..0000000 --- a/.history/scripts/init-db_20251019202258.js +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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.bulkCreate(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.count(); - - 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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202305.js b/.history/scripts/init-db_20251019202305.js deleted file mode 100644 index 9c2c5ce..0000000 --- a/.history/scripts/init-db_20251019202305.js +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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.bulkCreate(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.count(); - - 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.bulkCreate(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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202312.js b/.history/scripts/init-db_20251019202312.js deleted file mode 100644 index 15c017d..0000000 --- a/.history/scripts/init-db_20251019202312.js +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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.bulkCreate(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.count(); - - 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.bulkCreate(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 = await SiteSettings.create({ - 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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202318.js b/.history/scripts/init-db_20251019202318.js deleted file mode 100644 index 6ee7fc9..0000000 --- a/.history/scripts/init-db_20251019202318.js +++ /dev/null @@ -1,495 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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.bulkCreate(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.count(); - - 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.bulkCreate(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 = await SiteSettings.create({ - 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: '현재 시스템 점검 중입니다. 잠시 후 다시 접속해 주세요.' - } - }); - - 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 -}; \ No newline at end of file diff --git a/.history/scripts/init-db_20251019202631.js b/.history/scripts/init-db_20251019202631.js deleted file mode 100644 index 6ee7fc9..0000000 --- a/.history/scripts/init-db_20251019202631.js +++ /dev/null @@ -1,495 +0,0 @@ -#!/usr/bin/env node - -/** - * Database initialization script for SmartSolTech - * Creates initial admin user and sample data for PostgreSQL - */ - -const { sequelize } = require('../config/database'); -require('dotenv').config(); - -// Import models -const { User, Service, Portfolio, SiteSettings } = require('../models'); - -// Configuration -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 PostgreSQL...'); - await sequelize.authenticate(); - console.log('✅ Connected to PostgreSQL'); - - // Sync database (create tables) - console.log('🔄 Syncing database schema...'); - await sequelize.sync({ force: false }); - console.log('✅ Database schema synchronized'); - - // 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 sequelize.close(); - console.log('🔌 Database connection closed'); - process.exit(0); - } -} - -async function createAdminUser() { - try { - const existingAdmin = await User.findOne({ - where: { email: ADMIN_EMAIL } - }); - - if (existingAdmin) { - console.log('👤 Admin user already exists, skipping...'); - return; - } - - const adminUser = await User.create({ - name: 'Administrator', - email: ADMIN_EMAIL, - password: ADMIN_PASSWORD, - role: 'admin', - isActive: true - }); - - 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.count(); - - 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.bulkCreate(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.count(); - - 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.bulkCreate(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 = await SiteSettings.create({ - 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: '현재 시스템 점검 중입니다. 잠시 후 다시 접속해 주세요.' - } - }); - - 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 -}; \ No newline at end of file diff --git a/.history/scripts/sync-locales_20251021172132.js b/.history/scripts/sync-locales_20251021172132.js deleted file mode 100644 index e3f4eee..0000000 --- a/.history/scripts/sync-locales_20251021172132.js +++ /dev/null @@ -1,115 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const localesDir = path.join(__dirname, '..', 'locales'); -const files = fs.readdirSync(localesDir).filter(f => f.endsWith('.json')); - -// priority order for falling back when filling missing translations -const priority = ['en.json', 'ko.json', 'ru.json', 'kk.json']; - -function readJSON(file) { - try { - return JSON.parse(fs.readFileSync(path.join(localesDir, file), 'utf8')); - } catch (e) { - console.error('Failed to read', file, e.message); - return {}; - } -} - -function flatten(obj, prefix = '') { - const res = {}; - for (const k of Object.keys(obj)) { - const val = obj[k]; - const key = prefix ? `${prefix}.${k}` : k; - if (val && typeof val === 'object' && !Array.isArray(val)) { - Object.assign(res, flatten(val, key)); - } else { - res[key] = val; - } - } - return res; -} - -function unflatten(flat) { - const res = {}; - for (const flatKey of Object.keys(flat)) { - const parts = flatKey.split('.'); - let cur = res; - for (let i = 0; i < parts.length; i++) { - const p = parts[i]; - if (i === parts.length - 1) { - cur[p] = flat[flatKey]; - } else { - cur[p] = cur[p] || {}; - cur = cur[p]; - } - } - } - return res; -} - -// load all -const data = {}; -for (const f of files) { - data[f] = readJSON(f); -} - -// use en.json as canonical key set if exists, else merge all keys -let canonical = {}; -if (files.includes('en.json')) { - canonical = flatten(data['en.json']); -} else { - for (const f of files) { - canonical = Object.assign(canonical, flatten(data[f])); - } -} - -function getFallback(key, currentFile) { - for (const p of priority) { - if (p === currentFile) continue; - if (!files.includes(p)) continue; - const flat = flatten(data[p]); - if (flat[key] && typeof flat[key] === 'string' && flat[key].trim()) return flat[key]; - } - return null; -} - -let report = {}; -for (const f of files) { - const flat = flatten(data[f]); - report[f] = { added: 0, marked: 0 }; - const out = {}; - for (const key of Object.keys(canonical)) { - if (flat.hasOwnProperty(key)) { - out[key] = flat[key]; - } else { - const fb = getFallback(key, f); - if (fb) { - out[key] = fb; - } else { - out[key] = '[TRANSLATE] ' + canonical[key]; - report[f].marked++; - } - report[f].added++; - } - } - // also keep any extra keys present in this file but not in canonical - for (const key of Object.keys(flat)) { - if (!out.hasOwnProperty(key)) { - out[key] = flat[key]; - } - } - // write back - const nested = unflatten(out); - try { - fs.writeFileSync(path.join(localesDir, f), JSON.stringify(nested, null, 2), 'utf8'); - } catch (e) { - console.error('Failed to write', f, e.message); - } -} - -console.log('Locale sync report:'); -for (const f of files) { - console.log(`- ${f}: added ${report[f].added} keys, ${report[f].marked} marked for translation`); -} -console.log('Done. Review files in locales/ and replace [TRANSLATE] placeholders with correct translations.'); diff --git a/.history/scripts/sync-locales_20251021172239.js b/.history/scripts/sync-locales_20251021172239.js deleted file mode 100644 index e3f4eee..0000000 --- a/.history/scripts/sync-locales_20251021172239.js +++ /dev/null @@ -1,115 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const localesDir = path.join(__dirname, '..', 'locales'); -const files = fs.readdirSync(localesDir).filter(f => f.endsWith('.json')); - -// priority order for falling back when filling missing translations -const priority = ['en.json', 'ko.json', 'ru.json', 'kk.json']; - -function readJSON(file) { - try { - return JSON.parse(fs.readFileSync(path.join(localesDir, file), 'utf8')); - } catch (e) { - console.error('Failed to read', file, e.message); - return {}; - } -} - -function flatten(obj, prefix = '') { - const res = {}; - for (const k of Object.keys(obj)) { - const val = obj[k]; - const key = prefix ? `${prefix}.${k}` : k; - if (val && typeof val === 'object' && !Array.isArray(val)) { - Object.assign(res, flatten(val, key)); - } else { - res[key] = val; - } - } - return res; -} - -function unflatten(flat) { - const res = {}; - for (const flatKey of Object.keys(flat)) { - const parts = flatKey.split('.'); - let cur = res; - for (let i = 0; i < parts.length; i++) { - const p = parts[i]; - if (i === parts.length - 1) { - cur[p] = flat[flatKey]; - } else { - cur[p] = cur[p] || {}; - cur = cur[p]; - } - } - } - return res; -} - -// load all -const data = {}; -for (const f of files) { - data[f] = readJSON(f); -} - -// use en.json as canonical key set if exists, else merge all keys -let canonical = {}; -if (files.includes('en.json')) { - canonical = flatten(data['en.json']); -} else { - for (const f of files) { - canonical = Object.assign(canonical, flatten(data[f])); - } -} - -function getFallback(key, currentFile) { - for (const p of priority) { - if (p === currentFile) continue; - if (!files.includes(p)) continue; - const flat = flatten(data[p]); - if (flat[key] && typeof flat[key] === 'string' && flat[key].trim()) return flat[key]; - } - return null; -} - -let report = {}; -for (const f of files) { - const flat = flatten(data[f]); - report[f] = { added: 0, marked: 0 }; - const out = {}; - for (const key of Object.keys(canonical)) { - if (flat.hasOwnProperty(key)) { - out[key] = flat[key]; - } else { - const fb = getFallback(key, f); - if (fb) { - out[key] = fb; - } else { - out[key] = '[TRANSLATE] ' + canonical[key]; - report[f].marked++; - } - report[f].added++; - } - } - // also keep any extra keys present in this file but not in canonical - for (const key of Object.keys(flat)) { - if (!out.hasOwnProperty(key)) { - out[key] = flat[key]; - } - } - // write back - const nested = unflatten(out); - try { - fs.writeFileSync(path.join(localesDir, f), JSON.stringify(nested, null, 2), 'utf8'); - } catch (e) { - console.error('Failed to write', f, e.message); - } -} - -console.log('Locale sync report:'); -for (const f of files) { - console.log(`- ${f}: added ${report[f].added} keys, ${report[f].marked} marked for translation`); -} -console.log('Done. Review files in locales/ and replace [TRANSLATE] placeholders with correct translations.'); diff --git a/.history/server-demo_20251019163550.js b/.history/server-demo_20251019163550.js deleted file mode 100644 index 63a6c75..0000000 --- a/.history/server-demo_20251019163550.js +++ /dev/null @@ -1,416 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019163807.js b/.history/server-demo_20251019163807.js deleted file mode 100644 index 63a6c75..0000000 --- a/.history/server-demo_20251019163807.js +++ /dev/null @@ -1,416 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019164141.js b/.history/server-demo_20251019164141.js deleted file mode 100644 index 2dbb84e..0000000 --- a/.history/server-demo_20251019164141.js +++ /dev/null @@ -1,418 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019165556.js b/.history/server-demo_20251019165556.js deleted file mode 100644 index 2dbb84e..0000000 --- a/.history/server-demo_20251019165556.js +++ /dev/null @@ -1,418 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019165723.js b/.history/server-demo_20251019165723.js deleted file mode 100644 index d23037b..0000000 --- a/.history/server-demo_20251019165723.js +++ /dev/null @@ -1,419 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019170013.js b/.history/server-demo_20251019170013.js deleted file mode 100644 index d23037b..0000000 --- a/.history/server-demo_20251019170013.js +++ /dev/null @@ -1,419 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019170431.js b/.history/server-demo_20251019170431.js deleted file mode 100644 index f5af5a0..0000000 --- a/.history/server-demo_20251019170431.js +++ /dev/null @@ -1,426 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019170533.js b/.history/server-demo_20251019170533.js deleted file mode 100644 index f5af5a0..0000000 --- a/.history/server-demo_20251019170533.js +++ /dev/null @@ -1,426 +0,0 @@ -/** - * 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; \ No newline at end of file diff --git a/.history/server-demo_20251019170746.js b/.history/server-demo_20251019170746.js deleted file mode 100644 index 1d0e977..0000000 --- a/.history/server-demo_20251019170746.js +++ /dev/null @@ -1,427 +0,0 @@ -/** - * 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019170927.js b/.history/server-demo_20251019170927.js deleted file mode 100644 index 1d0e977..0000000 --- a/.history/server-demo_20251019170927.js +++ /dev/null @@ -1,427 +0,0 @@ -/** - * 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171746.js b/.history/server-demo_20251019171746.js deleted file mode 100644 index b6527bb..0000000 --- a/.history/server-demo_20251019171746.js +++ /dev/null @@ -1,440 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171805.js b/.history/server-demo_20251019171805.js deleted file mode 100644 index bb22ce6..0000000 --- a/.history/server-demo_20251019171805.js +++ /dev/null @@ -1,443 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171824.js b/.history/server-demo_20251019171824.js deleted file mode 100644 index 895e52e..0000000 --- a/.history/server-demo_20251019171824.js +++ /dev/null @@ -1,474 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171840.js b/.history/server-demo_20251019171840.js deleted file mode 100644 index 6640ff5..0000000 --- a/.history/server-demo_20251019171840.js +++ /dev/null @@ -1,485 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171853.js b/.history/server-demo_20251019171853.js deleted file mode 100644 index c14e838..0000000 --- a/.history/server-demo_20251019171853.js +++ /dev/null @@ -1,485 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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: res.__('navigation.portfolio') + ' - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171910.js b/.history/server-demo_20251019171910.js deleted file mode 100644 index 8840932..0000000 --- a/.history/server-demo_20251019171910.js +++ /dev/null @@ -1,486 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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: res.__('navigation.portfolio') + ' - 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 - ' + res.__('common.error'), - message: res.__('common.error'), - currentPage: 'error' - }); - } - - const relatedProjects = mockPortfolio.filter(p => - p._id !== portfolio._id && p.category === portfolio.category - ).slice(0, 3); - - res.render('portfolio-detail', { - title: `${portfolio.title} - ${res.__('navigation.portfolio')}`, - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171926.js b/.history/server-demo_20251019171926.js deleted file mode 100644 index 34a0f02..0000000 --- a/.history/server-demo_20251019171926.js +++ /dev/null @@ -1,486 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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: res.__('navigation.portfolio') + ' - 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 - ' + res.__('common.error'), - message: res.__('common.error'), - currentPage: 'error' - }); - } - - const relatedProjects = mockPortfolio.filter(p => - p._id !== portfolio._id && p.category === portfolio.category - ).slice(0, 3); - - res.render('portfolio-detail', { - title: `${portfolio.title} - ${res.__('navigation.portfolio')}`, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); -}); - -app.get('/services', (req, res) => { - res.render('services', { - title: res.__('navigation.services') + ' - SmartSolTech', - services: mockServices, - currentPage: 'services' - }); -}); - -app.get('/about', (req, res) => { - res.render('about', { - title: res.__('navigation.about') + ' - SmartSolTech', - currentPage: 'about' - }); -}); - -app.get('/contact', (req, res) => { - res.render('contact', { - title: res.__('navigation.contact') + ' - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019171941.js b/.history/server-demo_20251019171941.js deleted file mode 100644 index 0a61a18..0000000 --- a/.history/server-demo_20251019171941.js +++ /dev/null @@ -1,486 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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: res.__('navigation.portfolio') + ' - 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 - ' + res.__('common.error'), - message: res.__('common.error'), - currentPage: 'error' - }); - } - - const relatedProjects = mockPortfolio.filter(p => - p._id !== portfolio._id && p.category === portfolio.category - ).slice(0, 3); - - res.render('portfolio-detail', { - title: `${portfolio.title} - ${res.__('navigation.portfolio')}`, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); -}); - -app.get('/services', (req, res) => { - res.render('services', { - title: res.__('navigation.services') + ' - SmartSolTech', - services: mockServices, - currentPage: 'services' - }); -}); - -app.get('/about', (req, res) => { - res.render('about', { - title: res.__('navigation.about') + ' - SmartSolTech', - currentPage: 'about' - }); -}); - -app.get('/contact', (req, res) => { - res.render('contact', { - title: res.__('navigation.contact') + ' - 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.__('common.success')); - res.redirect('/contact'); -}); - -app.get('/calculator', (req, res) => { - res.render('calculator', { - title: res.__('navigation.calculator') + ' - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server-demo_20251019173728.js b/.history/server-demo_20251019173728.js deleted file mode 100644 index 0a61a18..0000000 --- a/.history/server-demo_20251019173728.js +++ /dev/null @@ -1,486 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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: res.__('navigation.portfolio') + ' - 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 - ' + res.__('common.error'), - message: res.__('common.error'), - currentPage: 'error' - }); - } - - const relatedProjects = mockPortfolio.filter(p => - p._id !== portfolio._id && p.category === portfolio.category - ).slice(0, 3); - - res.render('portfolio-detail', { - title: `${portfolio.title} - ${res.__('navigation.portfolio')}`, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); -}); - -app.get('/services', (req, res) => { - res.render('services', { - title: res.__('navigation.services') + ' - SmartSolTech', - services: mockServices, - currentPage: 'services' - }); -}); - -app.get('/about', (req, res) => { - res.render('about', { - title: res.__('navigation.about') + ' - SmartSolTech', - currentPage: 'about' - }); -}); - -app.get('/contact', (req, res) => { - res.render('contact', { - title: res.__('navigation.contact') + ' - 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.__('common.success')); - res.redirect('/contact'); -}); - -app.get('/calculator', (req, res) => { - res.render('calculator', { - title: res.__('navigation.calculator') + ' - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/.history/server_20251019160526.js b/.history/server_20251019160526.js deleted file mode 100644 index d17df23..0000000 --- a/.history/server_20251019160526.js +++ /dev/null @@ -1,121 +0,0 @@ -const express = require('express'); -const mongoose = require('mongoose'); -const session = require('express-session'); -const MongoStore = require('connect-mongo'); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection -mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', { - useNewUrlParser: true, - useUnifiedTopology: true, -}) -.then(() => console.log('✓ MongoDB connected')) -.catch(err => console.error('✗ MongoDB connection error:', err)); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: MongoStore.create({ - mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', - touchAfter: 24 * 3600 // lazy session update - }), - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); -}); - -module.exports = app; \ No newline at end of file diff --git a/.history/server_20251019162544.js b/.history/server_20251019162544.js deleted file mode 100644 index d17df23..0000000 --- a/.history/server_20251019162544.js +++ /dev/null @@ -1,121 +0,0 @@ -const express = require('express'); -const mongoose = require('mongoose'); -const session = require('express-session'); -const MongoStore = require('connect-mongo'); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection -mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', { - useNewUrlParser: true, - useUnifiedTopology: true, -}) -.then(() => console.log('✓ MongoDB connected')) -.catch(err => console.error('✗ MongoDB connection error:', err)); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: MongoStore.create({ - mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', - touchAfter: 24 * 3600 // lazy session update - }), - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); -}); - -module.exports = app; \ No newline at end of file diff --git a/.history/server_20251019201946.js b/.history/server_20251019201946.js deleted file mode 100644 index 9b233cc..0000000 --- a/.history/server_20251019201946.js +++ /dev/null @@ -1,121 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection -mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', { - useNewUrlParser: true, - useUnifiedTopology: true, -}) -.then(() => console.log('✓ MongoDB connected')) -.catch(err => console.error('✗ MongoDB connection error:', err)); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: MongoStore.create({ - mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', - touchAfter: 24 * 3600 // lazy session update - }), - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); -}); - -module.exports = app; \ No newline at end of file diff --git a/.history/server_20251019201954.js b/.history/server_20251019201954.js deleted file mode 100644 index 9f5dc0b..0000000 --- a/.history/server_20251019201954.js +++ /dev/null @@ -1,124 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: MongoStore.create({ - mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', - touchAfter: 24 * 3600 // lazy session update - }), - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); -}); - -module.exports = app; \ No newline at end of file diff --git a/.history/server_20251019202002.js b/.history/server_20251019202002.js deleted file mode 100644 index 2a9d835..0000000 --- a/.history/server_20251019202002.js +++ /dev/null @@ -1,121 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); -}); - -module.exports = app; \ No newline at end of file diff --git a/.history/server_20251019202011.js b/.history/server_20251019202011.js deleted file mode 100644 index a5313b2..0000000 --- a/.history/server_20251019202011.js +++ /dev/null @@ -1,137 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019202631.js b/.history/server_20251019202631.js deleted file mode 100644 index a5313b2..0000000 --- a/.history/server_20251019202631.js +++ /dev/null @@ -1,137 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ - success: false, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203324.js b/.history/server_20251019203324.js deleted file mode 100644 index f916b5e..0000000 --- a/.history/server_20251019203324.js +++ /dev/null @@ -1,138 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('404', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203331.js b/.history/server_20251019203331.js deleted file mode 100644 index e0b53bf..0000000 --- a/.history/server_20251019203331.js +++ /dev/null @@ -1,139 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203341.js b/.history/server_20251019203341.js deleted file mode 100644 index e0b53bf..0000000 --- a/.history/server_20251019203341.js +++ /dev/null @@ -1,139 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -require('dotenv').config(); - -const app = express(); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203555.js b/.history/server_20251019203555.js deleted file mode 100644 index 210a4f7..0000000 --- a/.history/server_20251019203555.js +++ /dev/null @@ -1,153 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203603.js b/.history/server_20251019203603.js deleted file mode 100644 index 4c06e0a..0000000 --- a/.history/server_20251019203603.js +++ /dev/null @@ -1,161 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session.theme || 'light'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203613.js b/.history/server_20251019203613.js deleted file mode 100644 index 0bccad5..0000000 --- a/.history/server_20251019203613.js +++ /dev/null @@ -1,167 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session.theme || 'light'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019203723.js b/.history/server_20251019203723.js deleted file mode 100644 index 0bccad5..0000000 --- a/.history/server_20251019203723.js +++ /dev/null @@ -1,167 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session.theme || 'light'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019204032.js b/.history/server_20251019204032.js deleted file mode 100644 index fc6cb35..0000000 --- a/.history/server_20251019204032.js +++ /dev/null @@ -1,168 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = req.getLocale(); - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019204034.js b/.history/server_20251019204034.js deleted file mode 100644 index fc6cb35..0000000 --- a/.history/server_20251019204034.js +++ /dev/null @@ -1,168 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = req.getLocale(); - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019204044.js b/.history/server_20251019204044.js deleted file mode 100644 index c7239ee..0000000 --- a/.history/server_20251019204044.js +++ /dev/null @@ -1,170 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = req.getLocale(); - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251019204050.js b/.history/server_20251019204050.js deleted file mode 100644 index c7239ee..0000000 --- a/.history/server_20251019204050.js +++ /dev/null @@ -1,170 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = req.getLocale(); - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020040200.js b/.history/server_20251020040200.js deleted file mode 100644 index 1dde76f..0000000 --- a/.history/server_20251020040200.js +++ /dev/null @@ -1,193 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - res.locals.locale = req.getLocale(); - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = req.getLocale(); - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020040210.js b/.history/server_20251020040210.js deleted file mode 100644 index 80454d0..0000000 --- a/.history/server_20251020040210.js +++ /dev/null @@ -1,197 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - Страница не найдена', - message: 'Запрашиваемая страница не найдена', - currentPage: 'error', - locale: req.getLocale ? req.getLocale() : 'ko', - __: res.__ || ((key) => key), - theme: req.session?.theme || 'light', - currentLanguage: req.getLocale ? req.getLocale() : 'ko' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020040221.js b/.history/server_20251020040221.js deleted file mode 100644 index bda65fc..0000000 --- a/.history/server_20251020040221.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020040538.js b/.history/server_20251020040538.js deleted file mode 100644 index bda65fc..0000000 --- a/.history/server_20251020040538.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020042047.js b/.history/server_20251020042047.js deleted file mode 100644 index 6b8d520..0000000 --- a/.history/server_20251020042047.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020042121.js b/.history/server_20251020042121.js deleted file mode 100644 index 6b8d520..0000000 --- a/.history/server_20251020042121.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020042412.js b/.history/server_20251020042412.js deleted file mode 100644 index 0f99a8d..0000000 --- a/.history/server_20251020042412.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020042429.js b/.history/server_20251020042429.js deleted file mode 100644 index 0f99a8d..0000000 --- a/.history/server_20251020042429.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ko', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020230447.js b/.history/server_20251020230447.js deleted file mode 100644 index e9992e9..0000000 --- a/.history/server_20251020230447.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020230451.js b/.history/server_20251020230451.js deleted file mode 100644 index e9992e9..0000000 --- a/.history/server_20251020230451.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ko'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020230454.js b/.history/server_20251020230454.js deleted file mode 100644 index 9a9762a..0000000 --- a/.history/server_20251020230454.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251020230455.js b/.history/server_20251020230455.js deleted file mode 100644 index 9a9762a..0000000 --- a/.history/server_20251020230455.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251021172422.js b/.history/server_20251021172422.js deleted file mode 100644 index bd58cd8..0000000 --- a/.history/server_20251021172422.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://cdn.tailwindcss.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com", "https://cdn.tailwindcss.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://cdn.tailwindcss.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251021172601.js b/.history/server_20251021172601.js deleted file mode 100644 index bd58cd8..0000000 --- a/.history/server_20251021172601.js +++ /dev/null @@ -1,191 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://cdn.tailwindcss.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com", "https://cdn.tailwindcss.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://cdn.tailwindcss.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251021213142.js b/.history/server_20251021213142.js deleted file mode 100644 index 1cfb86f..0000000 --- a/.history/server_20251021213142.js +++ /dev/null @@ -1,192 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://cdn.tailwindcss.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com", "https://cdn.tailwindcss.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://cdn.tailwindcss.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/api/admin', require('./routes/api/admin')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251021214112.js b/.history/server_20251021214112.js deleted file mode 100644 index 1cfb86f..0000000 --- a/.history/server_20251021214112.js +++ /dev/null @@ -1,192 +0,0 @@ -const express = require('express'); -const { sequelize, testConnection } = require('./config/database'); -const session = require('express-session'); -const SequelizeStore = require('connect-session-sequelize')(session.Store); -const path = require('path'); -const helmet = require('helmet'); -const compression = require('compression'); -const cors = require('cors'); -const morgan = require('morgan'); -const rateLimit = require('express-rate-limit'); -const i18n = require('i18n'); -require('dotenv').config(); - -const app = express(); - -// Настройка i18n -i18n.configure({ - locales: ['ko', 'en', 'ru', 'kk'], - defaultLocale: 'ru', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false -}); - -// i18n middleware -app.use(i18n.init); - -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - -// Security middleware -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://cdn.tailwindcss.com"], - fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], - scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com", "https://cdn.tailwindcss.com"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://cdn.tailwindcss.com"] - } - } -})); - -// Rate limiting -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100 // limit each IP to 100 requests per windowMs -}); -app.use('/api/', limiter); - -// Middleware -app.use(compression()); -app.use(cors()); -app.use(morgan('combined')); -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true, limit: '10mb' })); - -// Static files -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); - -// View engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); - -// Database connection and testing -testConnection(); - -// Session store configuration -const sessionStore = new SequelizeStore({ - db: sequelize, - tableName: 'sessions', - checkExpirationInterval: 15 * 60 * 1000, // 15 minutes - expiration: 7 * 24 * 60 * 60 * 1000 // 7 days -}); - -// Session configuration -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - secure: process.env.NODE_ENV === 'production', - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - } -})); - -// Routes -app.use('/', require('./routes/index')); -app.use('/api/auth', require('./routes/auth')); -app.use('/api/portfolio', require('./routes/portfolio')); -app.use('/api/services', require('./routes/services')); -app.use('/api/calculator', require('./routes/calculator')); -app.use('/api/contact', require('./routes/contact')); -app.use('/api/media', require('./routes/media')); -app.use('/api/admin', require('./routes/api/admin')); -app.use('/admin', require('./routes/admin')); - -// Language switching routes -app.get('/lang/:language', (req, res) => { - const { language } = req.params; - const supportedLanguages = ['ko', 'en', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - req.setLocale(language); - req.session.language = language; - } - - const referer = req.get('Referer') || '/'; - res.redirect(referer); -}); - -// Theme switching routes -app.get('/theme/:theme', (req, res) => { - const { theme } = req.params; - if (['light', 'dark'].includes(theme)) { - req.session.theme = theme; - } - res.json({ success: true, theme: req.session.theme }); -}); - -// PWA Service Worker -app.get('/sw.js', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'sw.js')); -}); - -// PWA Manifest -app.get('/manifest.json', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'manifest.json')); -}); - -// Error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).render('error', { - title: 'Error', - settings: {}, - message: process.env.NODE_ENV === 'production' - ? 'Something went wrong!' - : err.message, - currentPage: 'error' - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).render('error', { - title: '404 - 페이지를 찾을 수 없습니다', - settings: {}, - message: '요청하신 페이지를 찾을 수 없습니다', - currentPage: 'error' - }); -}); - -const PORT = process.env.PORT || 3000; - -// Sync database and start server -async function startServer() { - try { - // Sync all models with database - await sequelize.sync({ force: false }); - console.log('✓ Database synchronized'); - - // Create session table - await sessionStore.sync(); - console.log('✓ Session store synchronized'); - - app.listen(PORT, () => { - console.log(`🚀 Server running on port ${PORT}`); - console.log(`🌐 Visit: http://localhost:${PORT}`); - }); - } catch (error) { - console.error('✗ Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/.history/server_20251022052951.js b/.history/server_20251026092640.js similarity index 85% rename from .history/server_20251022052951.js rename to .history/server_20251026092640.js index 4c12c09..f6a1156 100644 --- a/.history/server_20251022052951.js +++ b/.history/server_20251026092640.js @@ -26,19 +26,6 @@ i18n.configure({ // i18n middleware app.use(i18n.init); -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - // Security middleware app.use(helmet({ contentSecurityPolicy: { @@ -106,6 +93,36 @@ app.use(session({ } })); +// Middleware для передачи переменных в шаблоны +app.use((req, res, next) => { + const currentLang = req.session?.language || req.getLocale() || 'ru'; + req.setLocale(currentLang); + + res.locals.locale = currentLang; + res.locals.__ = res.__; + res.locals.theme = req.session?.theme || 'light'; + res.locals.currentLanguage = currentLang; + res.locals.currentPage = req.path.split('/')[1] || 'home'; + + // Debug logging for theme + if (req.url !== '/sw.js' && !req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) { + console.log(`Request URL: ${req.url}, Theme from session: ${req.session?.theme}, Setting theme to: ${res.locals.theme}`); + } + + // Устанавливаем заголовки для предотвращения кеширования языкового контента + if (!req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + res.setHeader('Vary', 'Accept-Language'); + } + + // Отладочная информация + console.log(`Request URL: ${req.url}, Current Language: ${currentLang}, Session Language: ${req.session?.language}, Locale: ${req.getLocale()}`); + + next(); +}); + // Routes app.use('/', require('./routes/index')); app.use('/api/auth', require('./routes/auth')); @@ -125,6 +142,7 @@ app.get('/lang/:language', (req, res) => { if (supportedLanguages.includes(language)) { req.setLocale(language); req.session.language = language; + console.log(`Language switched to: ${language}, session language: ${req.session.language}`); } const referer = req.get('Referer') || '/'; diff --git a/.history/server_20251022052954.js b/.history/server_20251026092649.js similarity index 85% rename from .history/server_20251022052954.js rename to .history/server_20251026092649.js index 4c12c09..f6a1156 100644 --- a/.history/server_20251022052954.js +++ b/.history/server_20251026092649.js @@ -26,19 +26,6 @@ i18n.configure({ // i18n middleware app.use(i18n.init); -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - // Security middleware app.use(helmet({ contentSecurityPolicy: { @@ -106,6 +93,36 @@ app.use(session({ } })); +// Middleware для передачи переменных в шаблоны +app.use((req, res, next) => { + const currentLang = req.session?.language || req.getLocale() || 'ru'; + req.setLocale(currentLang); + + res.locals.locale = currentLang; + res.locals.__ = res.__; + res.locals.theme = req.session?.theme || 'light'; + res.locals.currentLanguage = currentLang; + res.locals.currentPage = req.path.split('/')[1] || 'home'; + + // Debug logging for theme + if (req.url !== '/sw.js' && !req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) { + console.log(`Request URL: ${req.url}, Theme from session: ${req.session?.theme}, Setting theme to: ${res.locals.theme}`); + } + + // Устанавливаем заголовки для предотвращения кеширования языкового контента + if (!req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + res.setHeader('Vary', 'Accept-Language'); + } + + // Отладочная информация + console.log(`Request URL: ${req.url}, Current Language: ${currentLang}, Session Language: ${req.session?.language}, Locale: ${req.getLocale()}`); + + next(); +}); + // Routes app.use('/', require('./routes/index')); app.use('/api/auth', require('./routes/auth')); @@ -125,6 +142,7 @@ app.get('/lang/:language', (req, res) => { if (supportedLanguages.includes(language)) { req.setLocale(language); req.session.language = language; + console.log(`Language switched to: ${language}, session language: ${req.session.language}`); } const referer = req.get('Referer') || '/'; diff --git a/.history/services/telegram_20251021213227.js b/.history/services/telegram_20251021213227.js deleted file mode 100644 index 687d0b6..0000000 --- a/.history/services/telegram_20251021213227.js +++ /dev/null @@ -1,163 +0,0 @@ -const axios = require('axios'); - -class TelegramService { - constructor() { - this.botToken = process.env.TELEGRAM_BOT_TOKEN; - this.chatId = process.env.TELEGRAM_CHAT_ID; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - } - - async sendMessage(text, options = {}) { - if (!this.isEnabled) { - console.warn('Telegram bot is not configured. Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID'); - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/sendMessage`, { - chat_id: this.chatId, - text: text, - parse_mode: 'HTML', - ...options - }); - - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram send message error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - async sendContactNotification(contact) { - const message = this.formatContactMessage(contact); - return await this.sendMessage(message); - } - - async sendNewContactAlert(contact) { - const message = `🔔 Новый запрос с сайта!\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `💰 Бюджет: ${contact.budget || 'Не указан'}\n` + - `⏱️ Сроки: ${contact.timeline || 'Не указаны'}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🕐 Время: ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Открыть в админ-панели`; - - return await this.sendMessage(message); - } - - async sendPortfolioNotification(portfolio) { - const message = `📁 Новый проект добавлен в портфолио\n\n` + - `🏷️ Название: ${portfolio.title}\n` + - `📂 Категория: ${portfolio.category}\n` + - `👤 Клиент: ${portfolio.clientName || 'Не указан'}\n` + - `🌐 URL: ${portfolio.projectUrl || 'Не указан'}\n` + - `⭐ Рекомендуемый: ${portfolio.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть проект`; - - return await this.sendMessage(message); - } - - async sendServiceNotification(service) { - const message = `⚙️ Новая услуга добавлена\n\n` + - `🏷️ Название: ${service.name}\n` + - `📂 Категория: ${service.category}\n` + - `💰 Стоимость: ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` + - `⏱️ Время выполнения: ${service.estimatedTime || 'Не указано'}\n` + - `⭐ Рекомендуемая: ${service.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть услуги`; - - return await this.sendMessage(message); - } - - async sendCalculatorQuote(calculatorData) { - const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0; - - const message = `💰 Новый расчет стоимости\n\n` + - `👤 Клиент: ${calculatorData.name || 'Не указан'}\n` + - `📧 Email: ${calculatorData.email || 'Не указан'}\n` + - `📱 Телефон: ${calculatorData.phone || 'Не указан'}\n\n` + - `🛠️ Выбранные услуги:\n${this.formatServices(calculatorData.services)}\n` + - `💵 Общая стоимость: $${totalCost}\n\n` + - `📅 Время: ${new Date().toLocaleString('ru-RU')}`; - - return await this.sendMessage(message); - } - - formatContactMessage(contact) { - return `📞 Уведомление о контакте\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `📊 Статус: ${this.getStatusText(contact.status)}\n` + - `⚡ Приоритет: ${this.getPriorityText(contact.priority)}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🔗 Открыть в админ-панели`; - } - - formatServices(services) { - if (!services || services.length === 0) return 'Не выбрано'; - - return services.map(service => - `• ${service.name} - $${service.price || 0}` - ).join('\n'); - } - - getStatusText(status) { - const statusMap = { - 'new': '🆕 Новое', - 'in_progress': '⏳ В работе', - 'completed': '✅ Завершено' - }; - return statusMap[status] || status; - } - - getPriorityText(priority) { - const priorityMap = { - 'low': '🟢 Низкий', - 'medium': '🟡 Средний', - 'high': '🔴 Высокий' - }; - return priorityMap[priority] || priority; - } - - // Test connection - async testConnection() { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - return { success: true, bot: response.data.result }; - } catch (error) { - console.error('Telegram connection test error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Webhook setup (for future use) - async setWebhook(webhookUrl) { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/setWebhook`, { - url: webhookUrl - }); - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram webhook setup error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } -} - -module.exports = new TelegramService(); \ No newline at end of file diff --git a/.history/services/telegram_20251021214113.js b/.history/services/telegram_20251021214113.js deleted file mode 100644 index 687d0b6..0000000 --- a/.history/services/telegram_20251021214113.js +++ /dev/null @@ -1,163 +0,0 @@ -const axios = require('axios'); - -class TelegramService { - constructor() { - this.botToken = process.env.TELEGRAM_BOT_TOKEN; - this.chatId = process.env.TELEGRAM_CHAT_ID; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - } - - async sendMessage(text, options = {}) { - if (!this.isEnabled) { - console.warn('Telegram bot is not configured. Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID'); - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/sendMessage`, { - chat_id: this.chatId, - text: text, - parse_mode: 'HTML', - ...options - }); - - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram send message error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - async sendContactNotification(contact) { - const message = this.formatContactMessage(contact); - return await this.sendMessage(message); - } - - async sendNewContactAlert(contact) { - const message = `🔔 Новый запрос с сайта!\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `💰 Бюджет: ${contact.budget || 'Не указан'}\n` + - `⏱️ Сроки: ${contact.timeline || 'Не указаны'}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🕐 Время: ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Открыть в админ-панели`; - - return await this.sendMessage(message); - } - - async sendPortfolioNotification(portfolio) { - const message = `📁 Новый проект добавлен в портфолио\n\n` + - `🏷️ Название: ${portfolio.title}\n` + - `📂 Категория: ${portfolio.category}\n` + - `👤 Клиент: ${portfolio.clientName || 'Не указан'}\n` + - `🌐 URL: ${portfolio.projectUrl || 'Не указан'}\n` + - `⭐ Рекомендуемый: ${portfolio.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть проект`; - - return await this.sendMessage(message); - } - - async sendServiceNotification(service) { - const message = `⚙️ Новая услуга добавлена\n\n` + - `🏷️ Название: ${service.name}\n` + - `📂 Категория: ${service.category}\n` + - `💰 Стоимость: ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` + - `⏱️ Время выполнения: ${service.estimatedTime || 'Не указано'}\n` + - `⭐ Рекомендуемая: ${service.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть услуги`; - - return await this.sendMessage(message); - } - - async sendCalculatorQuote(calculatorData) { - const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0; - - const message = `💰 Новый расчет стоимости\n\n` + - `👤 Клиент: ${calculatorData.name || 'Не указан'}\n` + - `📧 Email: ${calculatorData.email || 'Не указан'}\n` + - `📱 Телефон: ${calculatorData.phone || 'Не указан'}\n\n` + - `🛠️ Выбранные услуги:\n${this.formatServices(calculatorData.services)}\n` + - `💵 Общая стоимость: $${totalCost}\n\n` + - `📅 Время: ${new Date().toLocaleString('ru-RU')}`; - - return await this.sendMessage(message); - } - - formatContactMessage(contact) { - return `📞 Уведомление о контакте\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `📊 Статус: ${this.getStatusText(contact.status)}\n` + - `⚡ Приоритет: ${this.getPriorityText(contact.priority)}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🔗 Открыть в админ-панели`; - } - - formatServices(services) { - if (!services || services.length === 0) return 'Не выбрано'; - - return services.map(service => - `• ${service.name} - $${service.price || 0}` - ).join('\n'); - } - - getStatusText(status) { - const statusMap = { - 'new': '🆕 Новое', - 'in_progress': '⏳ В работе', - 'completed': '✅ Завершено' - }; - return statusMap[status] || status; - } - - getPriorityText(priority) { - const priorityMap = { - 'low': '🟢 Низкий', - 'medium': '🟡 Средний', - 'high': '🔴 Высокий' - }; - return priorityMap[priority] || priority; - } - - // Test connection - async testConnection() { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - return { success: true, bot: response.data.result }; - } catch (error) { - console.error('Telegram connection test error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Webhook setup (for future use) - async setWebhook(webhookUrl) { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/setWebhook`, { - url: webhookUrl - }); - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram webhook setup error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } -} - -module.exports = new TelegramService(); \ No newline at end of file diff --git a/.history/services/telegram_20251022194926.js b/.history/services/telegram_20251022194926.js deleted file mode 100644 index 3b641ae..0000000 --- a/.history/services/telegram_20251022194926.js +++ /dev/null @@ -1,295 +0,0 @@ -const axios = require('axios'); - -class TelegramService { - constructor() { - this.botToken = process.env.TELEGRAM_BOT_TOKEN; - this.chatId = process.env.TELEGRAM_CHAT_ID; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.chats = new Map(); // Store chat information - this.botInfo = null; // Store bot information - } - - // Update bot token and reinitialize - updateBotToken(newToken) { - this.botToken = newToken; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.botInfo = null; // Reset bot info - return this.testConnection(); - } - - // Update default chat ID - updateChatId(newChatId) { - this.chatId = newChatId; - this.isEnabled = !!(this.botToken && this.chatId); - } - - // Get bot information - async getBotInfo() { - if (this.botInfo) return { success: true, bot: this.botInfo }; - - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - this.botInfo = response.data.result; - return { success: true, bot: this.botInfo }; - } catch (error) { - console.error('Get bot info error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get updates (for getting chat IDs and managing chats) - async getUpdates() { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getUpdates`); - const updates = response.data.result; - - // Extract unique chats - const chats = new Map(); - updates.forEach(update => { - if (update.message) { - const chat = update.message.chat; - chats.set(chat.id, { - id: chat.id, - type: chat.type, - title: chat.title || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - username: chat.username || null, - first_name: chat.first_name || null, - last_name: chat.last_name || null, - description: chat.description || null, - invite_link: chat.invite_link || null, - pinned_message: chat.pinned_message || null, - permissions: chat.permissions || null, - slow_mode_delay: chat.slow_mode_delay || null - }); - } - }); - - // Update internal chat storage - chats.forEach((chat, id) => { - this.chats.set(id, chat); - }); - - return { - success: true, - updates, - chats: Array.from(chats.values()), - totalUpdates: updates.length - }; - } catch (error) { - console.error('Get updates error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat information - async getChat(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChat`, { - params: { chat_id: chatId } - }); - const chat = response.data.result; - - // Store in local cache - this.chats.set(chatId, chat); - - return { success: true, chat }; - } catch (error) { - console.error('Get chat error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat administrators - async getChatAdministrators(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChatAdministrators`, { - params: { chat_id: chatId } - }); - return { success: true, administrators: response.data.result }; - } catch (error) { - console.error('Get chat administrators error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get available chats (cached) - getAvailableChats() { - return Array.from(this.chats.values()).map(chat => ({ - id: chat.id, - title: chat.title || chat.username || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - type: chat.type, - username: chat.username - })); - } - - async sendMessage(text, options = {}) { - if (!this.isEnabled) { - console.warn('Telegram bot is not configured. Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID'); - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/sendMessage`, { - chat_id: this.chatId, - text: text, - parse_mode: 'HTML', - ...options - }); - - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram send message error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - async sendContactNotification(contact) { - const message = this.formatContactMessage(contact); - return await this.sendMessage(message); - } - - async sendNewContactAlert(contact) { - const message = `🔔 Новый запрос с сайта!\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `💰 Бюджет: ${contact.budget || 'Не указан'}\n` + - `⏱️ Сроки: ${contact.timeline || 'Не указаны'}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🕐 Время: ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Открыть в админ-панели`; - - return await this.sendMessage(message); - } - - async sendPortfolioNotification(portfolio) { - const message = `📁 Новый проект добавлен в портфолио\n\n` + - `🏷️ Название: ${portfolio.title}\n` + - `📂 Категория: ${portfolio.category}\n` + - `👤 Клиент: ${portfolio.clientName || 'Не указан'}\n` + - `🌐 URL: ${portfolio.projectUrl || 'Не указан'}\n` + - `⭐ Рекомендуемый: ${portfolio.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть проект`; - - return await this.sendMessage(message); - } - - async sendServiceNotification(service) { - const message = `⚙️ Новая услуга добавлена\n\n` + - `🏷️ Название: ${service.name}\n` + - `📂 Категория: ${service.category}\n` + - `💰 Стоимость: ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` + - `⏱️ Время выполнения: ${service.estimatedTime || 'Не указано'}\n` + - `⭐ Рекомендуемая: ${service.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть услуги`; - - return await this.sendMessage(message); - } - - async sendCalculatorQuote(calculatorData) { - const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0; - - const message = `💰 Новый расчет стоимости\n\n` + - `👤 Клиент: ${calculatorData.name || 'Не указан'}\n` + - `📧 Email: ${calculatorData.email || 'Не указан'}\n` + - `📱 Телефон: ${calculatorData.phone || 'Не указан'}\n\n` + - `🛠️ Выбранные услуги:\n${this.formatServices(calculatorData.services)}\n` + - `💵 Общая стоимость: $${totalCost}\n\n` + - `📅 Время: ${new Date().toLocaleString('ru-RU')}`; - - return await this.sendMessage(message); - } - - formatContactMessage(contact) { - return `📞 Уведомление о контакте\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `📊 Статус: ${this.getStatusText(contact.status)}\n` + - `⚡ Приоритет: ${this.getPriorityText(contact.priority)}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🔗 Открыть в админ-панели`; - } - - formatServices(services) { - if (!services || services.length === 0) return 'Не выбрано'; - - return services.map(service => - `• ${service.name} - $${service.price || 0}` - ).join('\n'); - } - - getStatusText(status) { - const statusMap = { - 'new': '🆕 Новое', - 'in_progress': '⏳ В работе', - 'completed': '✅ Завершено' - }; - return statusMap[status] || status; - } - - getPriorityText(priority) { - const priorityMap = { - 'low': '🟢 Низкий', - 'medium': '🟡 Средний', - 'high': '🔴 Высокий' - }; - return priorityMap[priority] || priority; - } - - // Test connection - async testConnection() { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - return { success: true, bot: response.data.result }; - } catch (error) { - console.error('Telegram connection test error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Webhook setup (for future use) - async setWebhook(webhookUrl) { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/setWebhook`, { - url: webhookUrl - }); - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram webhook setup error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } -} - -module.exports = new TelegramService(); \ No newline at end of file diff --git a/.history/services/telegram_20251022194946.js b/.history/services/telegram_20251022194946.js deleted file mode 100644 index e806e7d..0000000 --- a/.history/services/telegram_20251022194946.js +++ /dev/null @@ -1,365 +0,0 @@ -const axios = require('axios'); - -class TelegramService { - constructor() { - this.botToken = process.env.TELEGRAM_BOT_TOKEN; - this.chatId = process.env.TELEGRAM_CHAT_ID; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.chats = new Map(); // Store chat information - this.botInfo = null; // Store bot information - } - - // Update bot token and reinitialize - updateBotToken(newToken) { - this.botToken = newToken; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.botInfo = null; // Reset bot info - return this.testConnection(); - } - - // Update default chat ID - updateChatId(newChatId) { - this.chatId = newChatId; - this.isEnabled = !!(this.botToken && this.chatId); - } - - // Get bot information - async getBotInfo() { - if (this.botInfo) return { success: true, bot: this.botInfo }; - - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - this.botInfo = response.data.result; - return { success: true, bot: this.botInfo }; - } catch (error) { - console.error('Get bot info error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get updates (for getting chat IDs and managing chats) - async getUpdates() { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getUpdates`); - const updates = response.data.result; - - // Extract unique chats - const chats = new Map(); - updates.forEach(update => { - if (update.message) { - const chat = update.message.chat; - chats.set(chat.id, { - id: chat.id, - type: chat.type, - title: chat.title || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - username: chat.username || null, - first_name: chat.first_name || null, - last_name: chat.last_name || null, - description: chat.description || null, - invite_link: chat.invite_link || null, - pinned_message: chat.pinned_message || null, - permissions: chat.permissions || null, - slow_mode_delay: chat.slow_mode_delay || null - }); - } - }); - - // Update internal chat storage - chats.forEach((chat, id) => { - this.chats.set(id, chat); - }); - - return { - success: true, - updates, - chats: Array.from(chats.values()), - totalUpdates: updates.length - }; - } catch (error) { - console.error('Get updates error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat information - async getChat(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChat`, { - params: { chat_id: chatId } - }); - const chat = response.data.result; - - // Store in local cache - this.chats.set(chatId, chat); - - return { success: true, chat }; - } catch (error) { - console.error('Get chat error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat administrators - async getChatAdministrators(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChatAdministrators`, { - params: { chat_id: chatId } - }); - return { success: true, administrators: response.data.result }; - } catch (error) { - console.error('Get chat administrators error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get available chats (cached) - getAvailableChats() { - return Array.from(this.chats.values()).map(chat => ({ - id: chat.id, - title: chat.title || chat.username || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - type: chat.type, - username: chat.username - })); - } - - async sendMessage(text, options = {}) { - const chatId = options.chat_id || this.chatId; - - if (!this.botToken) { - console.warn('Telegram bot token is not configured'); - return { success: false, message: 'Telegram bot token not configured' }; - } - - if (!chatId) { - console.warn('Telegram chat ID is not specified'); - return { success: false, message: 'Telegram chat ID not specified' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/sendMessage`, { - chat_id: chatId, - text: text, - parse_mode: 'HTML', - disable_web_page_preview: false, - disable_notification: false, - ...options - }); - - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram send message error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Send message to multiple chats - async sendBroadcastMessage(text, chatIds = [], options = {}) { - if (!this.botToken) { - return { success: false, message: 'Telegram bot token not configured' }; - } - - if (!chatIds || chatIds.length === 0) { - return { success: false, message: 'No chat IDs specified' }; - } - - const results = []; - const errors = []; - - for (const chatId of chatIds) { - try { - const result = await this.sendMessage(text, { - ...options, - chat_id: chatId - }); - - if (result.success) { - results.push({ chatId, success: true, messageId: result.data.result.message_id }); - } else { - errors.push({ chatId, error: result.error || result.message }); - } - - // Add delay between messages to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - errors.push({ chatId, error: error.message }); - } - } - - return { - success: errors.length === 0, - results, - errors, - totalSent: results.length, - totalFailed: errors.length - }; - } - - // Send custom message with advanced options - async sendCustomMessage({ - text, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false, - replyMarkup = null - }) { - const targetChats = chatIds.length > 0 ? chatIds : [this.chatId]; - - return await this.sendBroadcastMessage(text, targetChats, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification, - reply_markup: replyMarkup - }); - } - - async sendContactNotification(contact) { - const message = this.formatContactMessage(contact); - return await this.sendMessage(message); - } - - async sendNewContactAlert(contact) { - const message = `🔔 Новый запрос с сайта!\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `💰 Бюджет: ${contact.budget || 'Не указан'}\n` + - `⏱️ Сроки: ${contact.timeline || 'Не указаны'}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🕐 Время: ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Открыть в админ-панели`; - - return await this.sendMessage(message); - } - - async sendPortfolioNotification(portfolio) { - const message = `📁 Новый проект добавлен в портфолио\n\n` + - `🏷️ Название: ${portfolio.title}\n` + - `📂 Категория: ${portfolio.category}\n` + - `👤 Клиент: ${portfolio.clientName || 'Не указан'}\n` + - `🌐 URL: ${portfolio.projectUrl || 'Не указан'}\n` + - `⭐ Рекомендуемый: ${portfolio.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть проект`; - - return await this.sendMessage(message); - } - - async sendServiceNotification(service) { - const message = `⚙️ Новая услуга добавлена\n\n` + - `🏷️ Название: ${service.name}\n` + - `📂 Категория: ${service.category}\n` + - `💰 Стоимость: ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` + - `⏱️ Время выполнения: ${service.estimatedTime || 'Не указано'}\n` + - `⭐ Рекомендуемая: ${service.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть услуги`; - - return await this.sendMessage(message); - } - - async sendCalculatorQuote(calculatorData) { - const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0; - - const message = `💰 Новый расчет стоимости\n\n` + - `👤 Клиент: ${calculatorData.name || 'Не указан'}\n` + - `📧 Email: ${calculatorData.email || 'Не указан'}\n` + - `📱 Телефон: ${calculatorData.phone || 'Не указан'}\n\n` + - `🛠️ Выбранные услуги:\n${this.formatServices(calculatorData.services)}\n` + - `💵 Общая стоимость: $${totalCost}\n\n` + - `📅 Время: ${new Date().toLocaleString('ru-RU')}`; - - return await this.sendMessage(message); - } - - formatContactMessage(contact) { - return `📞 Уведомление о контакте\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `📊 Статус: ${this.getStatusText(contact.status)}\n` + - `⚡ Приоритет: ${this.getPriorityText(contact.priority)}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🔗 Открыть в админ-панели`; - } - - formatServices(services) { - if (!services || services.length === 0) return 'Не выбрано'; - - return services.map(service => - `• ${service.name} - $${service.price || 0}` - ).join('\n'); - } - - getStatusText(status) { - const statusMap = { - 'new': '🆕 Новое', - 'in_progress': '⏳ В работе', - 'completed': '✅ Завершено' - }; - return statusMap[status] || status; - } - - getPriorityText(priority) { - const priorityMap = { - 'low': '🟢 Низкий', - 'medium': '🟡 Средний', - 'high': '🔴 Высокий' - }; - return priorityMap[priority] || priority; - } - - // Test connection - async testConnection() { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - return { success: true, bot: response.data.result }; - } catch (error) { - console.error('Telegram connection test error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Webhook setup (for future use) - async setWebhook(webhookUrl) { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/setWebhook`, { - url: webhookUrl - }); - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram webhook setup error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } -} - -module.exports = new TelegramService(); \ No newline at end of file diff --git a/.history/services/telegram_20251022194959.js b/.history/services/telegram_20251022194959.js deleted file mode 100644 index 1b5cbac..0000000 --- a/.history/services/telegram_20251022194959.js +++ /dev/null @@ -1,374 +0,0 @@ -const axios = require('axios'); - -class TelegramService { - constructor() { - this.botToken = process.env.TELEGRAM_BOT_TOKEN; - this.chatId = process.env.TELEGRAM_CHAT_ID; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.chats = new Map(); // Store chat information - this.botInfo = null; // Store bot information - } - - // Update bot token and reinitialize - updateBotToken(newToken) { - this.botToken = newToken; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.botInfo = null; // Reset bot info - return this.testConnection(); - } - - // Update default chat ID - updateChatId(newChatId) { - this.chatId = newChatId; - this.isEnabled = !!(this.botToken && this.chatId); - } - - // Get bot information - async getBotInfo() { - if (this.botInfo) return { success: true, bot: this.botInfo }; - - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - this.botInfo = response.data.result; - return { success: true, bot: this.botInfo }; - } catch (error) { - console.error('Get bot info error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get updates (for getting chat IDs and managing chats) - async getUpdates() { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getUpdates`); - const updates = response.data.result; - - // Extract unique chats - const chats = new Map(); - updates.forEach(update => { - if (update.message) { - const chat = update.message.chat; - chats.set(chat.id, { - id: chat.id, - type: chat.type, - title: chat.title || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - username: chat.username || null, - first_name: chat.first_name || null, - last_name: chat.last_name || null, - description: chat.description || null, - invite_link: chat.invite_link || null, - pinned_message: chat.pinned_message || null, - permissions: chat.permissions || null, - slow_mode_delay: chat.slow_mode_delay || null - }); - } - }); - - // Update internal chat storage - chats.forEach((chat, id) => { - this.chats.set(id, chat); - }); - - return { - success: true, - updates, - chats: Array.from(chats.values()), - totalUpdates: updates.length - }; - } catch (error) { - console.error('Get updates error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat information - async getChat(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChat`, { - params: { chat_id: chatId } - }); - const chat = response.data.result; - - // Store in local cache - this.chats.set(chatId, chat); - - return { success: true, chat }; - } catch (error) { - console.error('Get chat error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat administrators - async getChatAdministrators(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChatAdministrators`, { - params: { chat_id: chatId } - }); - return { success: true, administrators: response.data.result }; - } catch (error) { - console.error('Get chat administrators error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get available chats (cached) - getAvailableChats() { - return Array.from(this.chats.values()).map(chat => ({ - id: chat.id, - title: chat.title || chat.username || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - type: chat.type, - username: chat.username - })); - } - - async sendMessage(text, options = {}) { - const chatId = options.chat_id || this.chatId; - - if (!this.botToken) { - console.warn('Telegram bot token is not configured'); - return { success: false, message: 'Telegram bot token not configured' }; - } - - if (!chatId) { - console.warn('Telegram chat ID is not specified'); - return { success: false, message: 'Telegram chat ID not specified' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/sendMessage`, { - chat_id: chatId, - text: text, - parse_mode: 'HTML', - disable_web_page_preview: false, - disable_notification: false, - ...options - }); - - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram send message error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Send message to multiple chats - async sendBroadcastMessage(text, chatIds = [], options = {}) { - if (!this.botToken) { - return { success: false, message: 'Telegram bot token not configured' }; - } - - if (!chatIds || chatIds.length === 0) { - return { success: false, message: 'No chat IDs specified' }; - } - - const results = []; - const errors = []; - - for (const chatId of chatIds) { - try { - const result = await this.sendMessage(text, { - ...options, - chat_id: chatId - }); - - if (result.success) { - results.push({ chatId, success: true, messageId: result.data.result.message_id }); - } else { - errors.push({ chatId, error: result.error || result.message }); - } - - // Add delay between messages to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - errors.push({ chatId, error: error.message }); - } - } - - return { - success: errors.length === 0, - results, - errors, - totalSent: results.length, - totalFailed: errors.length - }; - } - - // Send custom message with advanced options - async sendCustomMessage({ - text, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false, - replyMarkup = null - }) { - const targetChats = chatIds.length > 0 ? chatIds : [this.chatId]; - - return await this.sendBroadcastMessage(text, targetChats, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification, - reply_markup: replyMarkup - }); - } - - async sendContactNotification(contact) { - const message = this.formatContactMessage(contact); - return await this.sendMessage(message); - } - - async sendNewContactAlert(contact) { - const message = `🔔 Новый запрос с сайта!\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `💰 Бюджет: ${contact.budget || 'Не указан'}\n` + - `⏱️ Сроки: ${contact.timeline || 'Не указаны'}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🕐 Время: ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Открыть в админ-панели`; - - return await this.sendMessage(message); - } - - async sendPortfolioNotification(portfolio) { - const message = `📁 Новый проект добавлен в портфолио\n\n` + - `🏷️ Название: ${portfolio.title}\n` + - `📂 Категория: ${portfolio.category}\n` + - `👤 Клиент: ${portfolio.clientName || 'Не указан'}\n` + - `🌐 URL: ${portfolio.projectUrl || 'Не указан'}\n` + - `⭐ Рекомендуемый: ${portfolio.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть проект`; - - return await this.sendMessage(message); - } - - async sendServiceNotification(service) { - const message = `⚙️ Новая услуга добавлена\n\n` + - `🏷️ Название: ${service.name}\n` + - `📂 Категория: ${service.category}\n` + - `💰 Стоимость: ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` + - `⏱️ Время выполнения: ${service.estimatedTime || 'Не указано'}\n` + - `⭐ Рекомендуемая: ${service.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть услуги`; - - return await this.sendMessage(message); - } - - async sendCalculatorQuote(calculatorData) { - const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0; - - const message = `💰 Новый расчет стоимости\n\n` + - `👤 Клиент: ${calculatorData.name || 'Не указан'}\n` + - `📧 Email: ${calculatorData.email || 'Не указан'}\n` + - `📱 Телефон: ${calculatorData.phone || 'Не указан'}\n\n` + - `🛠️ Выбранные услуги:\n${this.formatServices(calculatorData.services)}\n` + - `💵 Общая стоимость: $${totalCost}\n\n` + - `📅 Время: ${new Date().toLocaleString('ru-RU')}`; - - return await this.sendMessage(message); - } - - formatContactMessage(contact) { - return `📞 Уведомление о контакте\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `📊 Статус: ${this.getStatusText(contact.status)}\n` + - `⚡ Приоритет: ${this.getPriorityText(contact.priority)}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🔗 Открыть в админ-панели`; - } - - formatServices(services) { - if (!services || services.length === 0) return 'Не выбрано'; - - return services.map(service => - `• ${service.name} - $${service.price || 0}` - ).join('\n'); - } - - getStatusText(status) { - const statusMap = { - 'new': '🆕 Новое', - 'in_progress': '⏳ В работе', - 'completed': '✅ Завершено' - }; - return statusMap[status] || status; - } - - getPriorityText(priority) { - const priorityMap = { - 'low': '🟢 Низкий', - 'medium': '🟡 Средний', - 'high': '🔴 Высокий' - }; - return priorityMap[priority] || priority; - } - - // Test connection and update bot info - async testConnection() { - if (!this.botToken) { - return { success: false, message: 'Telegram bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - this.botInfo = response.data.result; - - // Also get updates to discover available chats - await this.getUpdates(); - - return { - success: true, - bot: this.botInfo, - availableChats: this.getAvailableChats() - }; - } catch (error) { - console.error('Telegram connection test error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Webhook setup (for future use) - async setWebhook(webhookUrl) { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/setWebhook`, { - url: webhookUrl - }); - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram webhook setup error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } -} - -module.exports = new TelegramService(); \ No newline at end of file diff --git a/.history/services/telegram_20251022195905.js b/.history/services/telegram_20251022195905.js deleted file mode 100644 index 1b5cbac..0000000 --- a/.history/services/telegram_20251022195905.js +++ /dev/null @@ -1,374 +0,0 @@ -const axios = require('axios'); - -class TelegramService { - constructor() { - this.botToken = process.env.TELEGRAM_BOT_TOKEN; - this.chatId = process.env.TELEGRAM_CHAT_ID; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.chats = new Map(); // Store chat information - this.botInfo = null; // Store bot information - } - - // Update bot token and reinitialize - updateBotToken(newToken) { - this.botToken = newToken; - this.baseUrl = `https://api.telegram.org/bot${this.botToken}`; - this.isEnabled = !!(this.botToken && this.chatId); - this.botInfo = null; // Reset bot info - return this.testConnection(); - } - - // Update default chat ID - updateChatId(newChatId) { - this.chatId = newChatId; - this.isEnabled = !!(this.botToken && this.chatId); - } - - // Get bot information - async getBotInfo() { - if (this.botInfo) return { success: true, bot: this.botInfo }; - - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - this.botInfo = response.data.result; - return { success: true, bot: this.botInfo }; - } catch (error) { - console.error('Get bot info error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get updates (for getting chat IDs and managing chats) - async getUpdates() { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getUpdates`); - const updates = response.data.result; - - // Extract unique chats - const chats = new Map(); - updates.forEach(update => { - if (update.message) { - const chat = update.message.chat; - chats.set(chat.id, { - id: chat.id, - type: chat.type, - title: chat.title || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - username: chat.username || null, - first_name: chat.first_name || null, - last_name: chat.last_name || null, - description: chat.description || null, - invite_link: chat.invite_link || null, - pinned_message: chat.pinned_message || null, - permissions: chat.permissions || null, - slow_mode_delay: chat.slow_mode_delay || null - }); - } - }); - - // Update internal chat storage - chats.forEach((chat, id) => { - this.chats.set(id, chat); - }); - - return { - success: true, - updates, - chats: Array.from(chats.values()), - totalUpdates: updates.length - }; - } catch (error) { - console.error('Get updates error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat information - async getChat(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChat`, { - params: { chat_id: chatId } - }); - const chat = response.data.result; - - // Store in local cache - this.chats.set(chatId, chat); - - return { success: true, chat }; - } catch (error) { - console.error('Get chat error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get chat administrators - async getChatAdministrators(chatId) { - if (!this.botToken) { - return { success: false, message: 'Bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getChatAdministrators`, { - params: { chat_id: chatId } - }); - return { success: true, administrators: response.data.result }; - } catch (error) { - console.error('Get chat administrators error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Get available chats (cached) - getAvailableChats() { - return Array.from(this.chats.values()).map(chat => ({ - id: chat.id, - title: chat.title || chat.username || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(), - type: chat.type, - username: chat.username - })); - } - - async sendMessage(text, options = {}) { - const chatId = options.chat_id || this.chatId; - - if (!this.botToken) { - console.warn('Telegram bot token is not configured'); - return { success: false, message: 'Telegram bot token not configured' }; - } - - if (!chatId) { - console.warn('Telegram chat ID is not specified'); - return { success: false, message: 'Telegram chat ID not specified' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/sendMessage`, { - chat_id: chatId, - text: text, - parse_mode: 'HTML', - disable_web_page_preview: false, - disable_notification: false, - ...options - }); - - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram send message error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Send message to multiple chats - async sendBroadcastMessage(text, chatIds = [], options = {}) { - if (!this.botToken) { - return { success: false, message: 'Telegram bot token not configured' }; - } - - if (!chatIds || chatIds.length === 0) { - return { success: false, message: 'No chat IDs specified' }; - } - - const results = []; - const errors = []; - - for (const chatId of chatIds) { - try { - const result = await this.sendMessage(text, { - ...options, - chat_id: chatId - }); - - if (result.success) { - results.push({ chatId, success: true, messageId: result.data.result.message_id }); - } else { - errors.push({ chatId, error: result.error || result.message }); - } - - // Add delay between messages to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - errors.push({ chatId, error: error.message }); - } - } - - return { - success: errors.length === 0, - results, - errors, - totalSent: results.length, - totalFailed: errors.length - }; - } - - // Send custom message with advanced options - async sendCustomMessage({ - text, - chatIds = [], - parseMode = 'HTML', - disableWebPagePreview = false, - disableNotification = false, - replyMarkup = null - }) { - const targetChats = chatIds.length > 0 ? chatIds : [this.chatId]; - - return await this.sendBroadcastMessage(text, targetChats, { - parse_mode: parseMode, - disable_web_page_preview: disableWebPagePreview, - disable_notification: disableNotification, - reply_markup: replyMarkup - }); - } - - async sendContactNotification(contact) { - const message = this.formatContactMessage(contact); - return await this.sendMessage(message); - } - - async sendNewContactAlert(contact) { - const message = `🔔 Новый запрос с сайта!\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `💰 Бюджет: ${contact.budget || 'Не указан'}\n` + - `⏱️ Сроки: ${contact.timeline || 'Не указаны'}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🕐 Время: ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Открыть в админ-панели`; - - return await this.sendMessage(message); - } - - async sendPortfolioNotification(portfolio) { - const message = `📁 Новый проект добавлен в портфолио\n\n` + - `🏷️ Название: ${portfolio.title}\n` + - `📂 Категория: ${portfolio.category}\n` + - `👤 Клиент: ${portfolio.clientName || 'Не указан'}\n` + - `🌐 URL: ${portfolio.projectUrl || 'Не указан'}\n` + - `⭐ Рекомендуемый: ${portfolio.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть проект`; - - return await this.sendMessage(message); - } - - async sendServiceNotification(service) { - const message = `⚙️ Новая услуга добавлена\n\n` + - `🏷️ Название: ${service.name}\n` + - `📂 Категория: ${service.category}\n` + - `💰 Стоимость: ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` + - `⏱️ Время выполнения: ${service.estimatedTime || 'Не указано'}\n` + - `⭐ Рекомендуемая: ${service.featured ? 'Да' : 'Нет'}\n` + - `📅 Время: ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` + - `🔗 Посмотреть услуги`; - - return await this.sendMessage(message); - } - - async sendCalculatorQuote(calculatorData) { - const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0; - - const message = `💰 Новый расчет стоимости\n\n` + - `👤 Клиент: ${calculatorData.name || 'Не указан'}\n` + - `📧 Email: ${calculatorData.email || 'Не указан'}\n` + - `📱 Телефон: ${calculatorData.phone || 'Не указан'}\n\n` + - `🛠️ Выбранные услуги:\n${this.formatServices(calculatorData.services)}\n` + - `💵 Общая стоимость: $${totalCost}\n\n` + - `📅 Время: ${new Date().toLocaleString('ru-RU')}`; - - return await this.sendMessage(message); - } - - formatContactMessage(contact) { - return `📞 Уведомление о контакте\n\n` + - `👤 Клиент: ${contact.name}\n` + - `📧 Email: ${contact.email}\n` + - `📱 Телефон: ${contact.phone || 'Не указан'}\n` + - `💼 Услуга: ${contact.serviceInterest || 'Общий запрос'}\n` + - `📊 Статус: ${this.getStatusText(contact.status)}\n` + - `⚡ Приоритет: ${this.getPriorityText(contact.priority)}\n\n` + - `💬 Сообщение:\n${contact.message}\n\n` + - `🔗 Открыть в админ-панели`; - } - - formatServices(services) { - if (!services || services.length === 0) return 'Не выбрано'; - - return services.map(service => - `• ${service.name} - $${service.price || 0}` - ).join('\n'); - } - - getStatusText(status) { - const statusMap = { - 'new': '🆕 Новое', - 'in_progress': '⏳ В работе', - 'completed': '✅ Завершено' - }; - return statusMap[status] || status; - } - - getPriorityText(priority) { - const priorityMap = { - 'low': '🟢 Низкий', - 'medium': '🟡 Средний', - 'high': '🔴 Высокий' - }; - return priorityMap[priority] || priority; - } - - // Test connection and update bot info - async testConnection() { - if (!this.botToken) { - return { success: false, message: 'Telegram bot token not configured' }; - } - - try { - const response = await axios.get(`${this.baseUrl}/getMe`); - this.botInfo = response.data.result; - - // Also get updates to discover available chats - await this.getUpdates(); - - return { - success: true, - bot: this.botInfo, - availableChats: this.getAvailableChats() - }; - } catch (error) { - console.error('Telegram connection test error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } - - // Webhook setup (for future use) - async setWebhook(webhookUrl) { - if (!this.isEnabled) { - return { success: false, message: 'Telegram bot not configured' }; - } - - try { - const response = await axios.post(`${this.baseUrl}/setWebhook`, { - url: webhookUrl - }); - return { success: true, data: response.data }; - } catch (error) { - console.error('Telegram webhook setup error:', error.response?.data || error.message); - return { success: false, error: error.message }; - } - } -} - -module.exports = new TelegramService(); \ No newline at end of file diff --git a/.history/tailwind.config_20251025213049.js b/.history/tailwind.config_20251025213049.js new file mode 100644 index 0000000..2e732a6 --- /dev/null +++ b/.history/tailwind.config_20251025213049.js @@ -0,0 +1,83 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./views/**/*.ejs", + "./public/**/*.js", + "./public/**/*.html" + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a' + }, + secondary: { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a' + } + }, + fontFamily: { + sans: ['Ubuntu', 'system-ui', 'sans-serif'], + display: ['Ubuntu', 'system-ui', 'sans-serif'] + }, + fontSize: { + 'xs': ['0.625rem', { lineHeight: '0.875rem' }], + 'sm': ['0.75rem', { lineHeight: '1rem' }], + 'base': ['0.825rem', { lineHeight: '1.125rem' }], + 'lg': ['0.9rem', { lineHeight: '1.25rem' }], + 'xl': ['1rem', { lineHeight: '1.375rem' }], + '2xl': ['1.125rem', { lineHeight: '1.5rem' }], + '3xl': ['1.375rem', { lineHeight: '1.75rem' }], + '4xl': ['1.625rem', { lineHeight: '2rem' }], + '5xl': ['2rem', { lineHeight: '2.5rem' }], + '6xl': ['2.5rem', { lineHeight: '3rem' }], + '7xl': ['3rem', { lineHeight: '3.5rem' }], + '8xl': ['4rem', { lineHeight: '4.5rem' }], + '9xl': ['5rem', { lineHeight: '5.5rem' }] + }, + spacing: { + '72': '18rem', + '84': '21rem', + '96': '24rem' + }, + animation: { + 'fade-in': 'fadeIn 0.5s ease-in-out', + 'slide-in': 'slideIn 0.3s ease-out', + 'bounce-in': 'bounceIn 0.6s ease-out' + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + slideIn: { + '0%': { transform: 'translateY(-10px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' } + }, + bounceIn: { + '0%': { transform: 'scale(0.3)', opacity: '0' }, + '50%': { transform: 'scale(1.05)', opacity: '0.8' }, + '100%': { transform: 'scale(1)', opacity: '1' } + } + } + }, + }, + plugins: [] +} \ No newline at end of file diff --git a/.history/tailwind.config_20251025213357.js b/.history/tailwind.config_20251025213357.js new file mode 100644 index 0000000..2e732a6 --- /dev/null +++ b/.history/tailwind.config_20251025213357.js @@ -0,0 +1,83 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./views/**/*.ejs", + "./public/**/*.js", + "./public/**/*.html" + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a' + }, + secondary: { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a' + } + }, + fontFamily: { + sans: ['Ubuntu', 'system-ui', 'sans-serif'], + display: ['Ubuntu', 'system-ui', 'sans-serif'] + }, + fontSize: { + 'xs': ['0.625rem', { lineHeight: '0.875rem' }], + 'sm': ['0.75rem', { lineHeight: '1rem' }], + 'base': ['0.825rem', { lineHeight: '1.125rem' }], + 'lg': ['0.9rem', { lineHeight: '1.25rem' }], + 'xl': ['1rem', { lineHeight: '1.375rem' }], + '2xl': ['1.125rem', { lineHeight: '1.5rem' }], + '3xl': ['1.375rem', { lineHeight: '1.75rem' }], + '4xl': ['1.625rem', { lineHeight: '2rem' }], + '5xl': ['2rem', { lineHeight: '2.5rem' }], + '6xl': ['2.5rem', { lineHeight: '3rem' }], + '7xl': ['3rem', { lineHeight: '3.5rem' }], + '8xl': ['4rem', { lineHeight: '4.5rem' }], + '9xl': ['5rem', { lineHeight: '5.5rem' }] + }, + spacing: { + '72': '18rem', + '84': '21rem', + '96': '24rem' + }, + animation: { + 'fade-in': 'fadeIn 0.5s ease-in-out', + 'slide-in': 'slideIn 0.3s ease-out', + 'bounce-in': 'bounceIn 0.6s ease-out' + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + slideIn: { + '0%': { transform: 'translateY(-10px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' } + }, + bounceIn: { + '0%': { transform: 'scale(0.3)', opacity: '0' }, + '50%': { transform: 'scale(1.05)', opacity: '0.8' }, + '100%': { transform: 'scale(1)', opacity: '1' } + } + } + }, + }, + plugins: [] +} \ No newline at end of file diff --git a/.history/views/about-new_20251019174332.ejs b/.history/views/about-new_20251019174332.ejs deleted file mode 100644 index 6954758..0000000 --- a/.history/views/about-new_20251019174332.ejs +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('about.hero.title') %> -

-

- <%- __('about.hero.subtitle') %> -

-
-
- - -
-
-
-
-

- <%- __('about.company.title') %> -

-

- <%- __('about.company.description1') %> -

-

- <%- __('about.company.description2') %> -

- - -
-
-
50+
-
<%- __('about.stats.projects') %>
-
-
-
3+
-
<%- __('about.stats.experience') %>
-
-
-
30+
-
<%- __('about.stats.clients') %>
-
-
-
- - -
-
- -
-
-
-
-
- - -
-
-
-

- <%- __('about.mission.title') %> -

-

- <%- __('about.mission.description') %> -

-
- -
- -
-
- -
-

- <%- __('about.values.innovation.title') %> -

-

- <%- __('about.values.innovation.description') %> -

-
- - -
-
- -
-

- <%- __('about.values.quality.title') %> -

-

- <%- __('about.values.quality.description') %> -

-
- - -
-
- -
-

- <%- __('about.values.partnership.title') %> -

-

- <%- __('about.values.partnership.description') %> -

-
-
-
-
- - -
-
-

- <%- __('about.cta.title') %> -

-

- <%- __('about.cta.subtitle') %> -

- - <%- __('about.cta.button') %> - - - - -
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/about-new_20251019174544.ejs b/.history/views/about-new_20251019174544.ejs deleted file mode 100644 index 6954758..0000000 --- a/.history/views/about-new_20251019174544.ejs +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('about.hero.title') %> -

-

- <%- __('about.hero.subtitle') %> -

-
-
- - -
-
-
-
-

- <%- __('about.company.title') %> -

-

- <%- __('about.company.description1') %> -

-

- <%- __('about.company.description2') %> -

- - -
-
-
50+
-
<%- __('about.stats.projects') %>
-
-
-
3+
-
<%- __('about.stats.experience') %>
-
-
-
30+
-
<%- __('about.stats.clients') %>
-
-
-
- - -
-
- -
-
-
-
-
- - -
-
-
-

- <%- __('about.mission.title') %> -

-

- <%- __('about.mission.description') %> -

-
- -
- -
-
- -
-

- <%- __('about.values.innovation.title') %> -

-

- <%- __('about.values.innovation.description') %> -

-
- - -
-
- -
-

- <%- __('about.values.quality.title') %> -

-

- <%- __('about.values.quality.description') %> -

-
- - -
-
- -
-

- <%- __('about.values.partnership.title') %> -

-

- <%- __('about.values.partnership.description') %> -

-
-
-
-
- - -
-
-

- <%- __('about.cta.title') %> -

-

- <%- __('about.cta.subtitle') %> -

- - <%- __('about.cta.button') %> - - - - -
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/about_20251019170417.ejs b/.history/views/about_20251019170417.ejs deleted file mode 100644 index 9dfbf5b..0000000 --- a/.history/views/about_20251019170417.ejs +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - 회사 소개 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- About SmartSolTech -

-

- 혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업 -

-
-
- - -
-
-
-
-

- 혁신창의로 만드는 미래 -

-

- SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 - 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다. -

-

- 우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 - 제안하여 함께 성장하는 파트너가 되고자 합니다. -

-
-
-
100+
-
완료 프로젝트
-
-
-
50+
-
만족한 고객
-
-
-
4년
-
업계 경험
-
-
-
-
-
-
-

우리의 미션

-

- "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것" -

-

우리의 비전

-

- "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 - 전 세계 고객들의 디지털 혁신을 주도하는 것" -

-
-
-
-
-
-
-
-
- - -
-
-
-

- Core Values -

-

- SmartSolTech가 추구하는 핵심 가치들 -

-
- -
-
-
- -
-

혁신 (Innovation)

-

- 끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다. -

-
- -
-
- -
-

협력 (Collaboration)

-

- 고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다. -

-
- -
-
- -
-

품질 (Quality)

-

- 높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다. -

-
- -
-
- -
-

성장 (Growth)

-

- 고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다. -

-
-
-
-
- - -
-
-
-

- Our Team -

-

- 전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다 -

-
- -
-
-
- KH -
-

김현우

-

CEO & Founder

-

- 10년 이상의 웹 개발 경험을 바탕으로 SmartSolTech를 설립하여 - 혁신적인 디지털 솔루션을 제공하고 있습니다. -

- -
- -
-
- LJ -
-

이정민

-

CTO & Lead Developer

-

- 풀스택 개발자로서 최신 기술 트렌드를 적용하여 - 확장 가능하고 안정적인 시스템을 구축합니다. -

- -
- -
-
- PS -
-

박서연

-

UI/UX Designer

-

- 사용자 중심의 디자인 철학을 바탕으로 직관적이고 - 아름다운 인터페이스를 설계합니다. -

- -
-
-
-
- - -
-
-
-

- Technology Stack -

-

- 최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다 -

-
- -
-
-

Frontend

-
-
- -
React
-
-
- -
Vue.js
-
-
- -
Angular
-
-
-
- -
-

Backend

-
-
- -
Node.js
-
-
- -
Python
-
-
- -
Java
-
-
-
- -
-

Mobile

-
-
- -
React Native
-
-
- -
Flutter
-
-
- -
Swift
-
-
-
-
-
-
- - -
-
-
-

- 함께 성공하는 파트너가 되어보세요 -

-

- SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요 -

- -
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/about_20251019170533.ejs b/.history/views/about_20251019170533.ejs deleted file mode 100644 index 9dfbf5b..0000000 --- a/.history/views/about_20251019170533.ejs +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - 회사 소개 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- About SmartSolTech -

-

- 혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업 -

-
-
- - -
-
-
-
-

- 혁신창의로 만드는 미래 -

-

- SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 - 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다. -

-

- 우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 - 제안하여 함께 성장하는 파트너가 되고자 합니다. -

-
-
-
100+
-
완료 프로젝트
-
-
-
50+
-
만족한 고객
-
-
-
4년
-
업계 경험
-
-
-
-
-
-
-

우리의 미션

-

- "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것" -

-

우리의 비전

-

- "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 - 전 세계 고객들의 디지털 혁신을 주도하는 것" -

-
-
-
-
-
-
-
-
- - -
-
-
-

- Core Values -

-

- SmartSolTech가 추구하는 핵심 가치들 -

-
- -
-
-
- -
-

혁신 (Innovation)

-

- 끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다. -

-
- -
-
- -
-

협력 (Collaboration)

-

- 고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다. -

-
- -
-
- -
-

품질 (Quality)

-

- 높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다. -

-
- -
-
- -
-

성장 (Growth)

-

- 고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다. -

-
-
-
-
- - -
-
-
-

- Our Team -

-

- 전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다 -

-
- -
-
-
- KH -
-

김현우

-

CEO & Founder

-

- 10년 이상의 웹 개발 경험을 바탕으로 SmartSolTech를 설립하여 - 혁신적인 디지털 솔루션을 제공하고 있습니다. -

- -
- -
-
- LJ -
-

이정민

-

CTO & Lead Developer

-

- 풀스택 개발자로서 최신 기술 트렌드를 적용하여 - 확장 가능하고 안정적인 시스템을 구축합니다. -

- -
- -
-
- PS -
-

박서연

-

UI/UX Designer

-

- 사용자 중심의 디자인 철학을 바탕으로 직관적이고 - 아름다운 인터페이스를 설계합니다. -

- -
-
-
-
- - -
-
-
-

- Technology Stack -

-

- 최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다 -

-
- -
-
-

Frontend

-
-
- -
React
-
-
- -
Vue.js
-
-
- -
Angular
-
-
-
- -
-

Backend

-
-
- -
Node.js
-
-
- -
Python
-
-
- -
Java
-
-
-
- -
-

Mobile

-
-
- -
React Native
-
-
- -
Flutter
-
-
- -
Swift
-
-
-
-
-
-
- - -
-
-
-

- 함께 성공하는 파트너가 되어보세요 -

-

- SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요 -

- -
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/about_20251019171021.ejs b/.history/views/about_20251019171021.ejs deleted file mode 100644 index 0974833..0000000 --- a/.history/views/about_20251019171021.ejs +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - 회사 소개 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- About SmartSolTech -

-

- 혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업 -

-
-
- - -
-
-
-
-

- 혁신창의로 만드는 미래 -

-

- SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 - 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다. -

-

- 우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 - 제안하여 함께 성장하는 파트너가 되고자 합니다. -

-
-
-
100+
-
완료 프로젝트
-
-
-
50+
-
만족한 고객
-
-
-
4년
-
업계 경험
-
-
-
-
-
-
-

우리의 미션

-

- "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것" -

-

우리의 비전

-

- "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 - 전 세계 고객들의 디지털 혁신을 주도하는 것" -

-
-
-
-
-
-
-
-
- - -
-
-
-

- Core Values -

-

- SmartSolTech가 추구하는 핵심 가치들 -

-
- -
-
-
- -
-

혁신 (Innovation)

-

- 끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다. -

-
- -
-
- -
-

협력 (Collaboration)

-

- 고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다. -

-
- -
-
- -
-

품질 (Quality)

-

- 높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다. -

-
- -
-
- -
-

성장 (Growth)

-

- 고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다. -

-
-
-
-
- - -
-
-
-

- Our Team -

-

- 전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다 -

-
- -
-
-
- KH -
-

김현우

-

CEO & Founder

-

- 10년 이상의 웹 개발 경험을 바탕으로 SmartSolTech를 설립하여 - 혁신적인 디지털 솔루션을 제공하고 있습니다. -

- -
- -
-
- LJ -
-

이정민

-

CTO & Lead Developer

-

- 풀스택 개발자로서 최신 기술 트렌드를 적용하여 - 확장 가능하고 안정적인 시스템을 구축합니다. -

- -
- -
-
- PS -
-

박서연

-

UI/UX Designer

-

- 사용자 중심의 디자인 철학을 바탕으로 직관적이고 - 아름다운 인터페이스를 설계합니다. -

- -
-
-
-
- - -
-
-
-

- Technology Stack -

-

- 최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다 -

-
- -
-
-

Frontend

-
-
- -
React
-
-
- -
Vue.js
-
-
- -
Angular
-
-
-
- -
-

Backend

-
-
- -
Node.js
-
-
- -
Python
-
-
- -
Java
-
-
-
- -
-

Mobile

-
-
- -
React Native
-
-
- -
Flutter
-
-
- -
Swift
-
-
-
-
-
-
- - -
-
-
-

- 함께 성공하는 파트너가 되어보세요 -

-

- SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요 -

- -
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/about_20251019171203.ejs b/.history/views/about_20251019171203.ejs deleted file mode 100644 index 0974833..0000000 --- a/.history/views/about_20251019171203.ejs +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - 회사 소개 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- About SmartSolTech -

-

- 혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업 -

-
-
- - -
-
-
-
-

- 혁신창의로 만드는 미래 -

-

- SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 - 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다. -

-

- 우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 - 제안하여 함께 성장하는 파트너가 되고자 합니다. -

-
-
-
100+
-
완료 프로젝트
-
-
-
50+
-
만족한 고객
-
-
-
4년
-
업계 경험
-
-
-
-
-
-
-

우리의 미션

-

- "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것" -

-

우리의 비전

-

- "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 - 전 세계 고객들의 디지털 혁신을 주도하는 것" -

-
-
-
-
-
-
-
-
- - -
-
-
-

- Core Values -

-

- SmartSolTech가 추구하는 핵심 가치들 -

-
- -
-
-
- -
-

혁신 (Innovation)

-

- 끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다. -

-
- -
-
- -
-

협력 (Collaboration)

-

- 고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다. -

-
- -
-
- -
-

품질 (Quality)

-

- 높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다. -

-
- -
-
- -
-

성장 (Growth)

-

- 고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다. -

-
-
-
-
- - -
-
-
-

- Our Team -

-

- 전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다 -

-
- -
-
-
- KH -
-

김현우

-

CEO & Founder

-

- 10년 이상의 웹 개발 경험을 바탕으로 SmartSolTech를 설립하여 - 혁신적인 디지털 솔루션을 제공하고 있습니다. -

- -
- -
-
- LJ -
-

이정민

-

CTO & Lead Developer

-

- 풀스택 개발자로서 최신 기술 트렌드를 적용하여 - 확장 가능하고 안정적인 시스템을 구축합니다. -

- -
- -
-
- PS -
-

박서연

-

UI/UX Designer

-

- 사용자 중심의 디자인 철학을 바탕으로 직관적이고 - 아름다운 인터페이스를 설계합니다. -

- -
-
-
-
- - -
-
-
-

- Technology Stack -

-

- 최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다 -

-
- -
-
-

Frontend

-
-
- -
React
-
-
- -
Vue.js
-
-
- -
Angular
-
-
-
- -
-

Backend

-
-
- -
Node.js
-
-
- -
Python
-
-
- -
Java
-
-
-
- -
-

Mobile

-
-
- -
React Native
-
-
- -
Flutter
-
-
- -
Swift
-
-
-
-
-
-
- - -
-
-
-

- 함께 성공하는 파트너가 되어보세요 -

-

- SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요 -

- -
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/about_20251020225419.ejs b/.history/views/about_20251020225419.ejs deleted file mode 100644 index 77a2b0c..0000000 --- a/.history/views/about_20251020225419.ejs +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('about.hero.title') %> -

-

- <%- __('about.hero.subtitle') %> -

-
-
- - -
-
-
-
-

- <%- __('about.company.title') %> -

-

- <%- __('about.company.description1') %> -

-

- <%- __('about.company.description2') %> -

- - -
-
-
50+
-
<%- __('about.stats.projects') %>
-
-
-
3+
-
<%- __('about.stats.experience') %>
-
-
-
30+
-
<%- __('about.stats.clients') %>
-
-
-
- - -
-
- -
-
-
-
-
- - -
-
-
-

- <%- __('about.mission.title') %> -

-

- <%- __('about.mission.description') %> -

-
- -
- -
-
- -
-

- <%- __('about.values.innovation.title') %> -

-

- <%- __('about.values.innovation.description') %> -

-
- - -
-
- -
-

- <%- __('about.values.quality.title') %> -

-

- <%- __('about.values.quality.description') %> -

-
- - -
-
- -
-

- <%- __('about.values.partnership.title') %> -

-

- <%- __('about.values.partnership.description') %> -

-
-
-
-
- - -
-
-

- <%- __('about.cta.title') %> -

-

- <%- __('about.cta.subtitle') %> -

- - <%- __('about.cta.button') %> - - - - -
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/about_20251021212419.ejs b/.history/views/about_20251025214146.ejs similarity index 82% rename from .history/views/about_20251021212419.ejs rename to .history/views/about_20251025214146.ejs index 8a673aa..46f6e40 100644 --- a/.history/views/about_20251021212419.ejs +++ b/.history/views/about_20251025214146.ejs @@ -1,22 +1,4 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> +
@@ -39,10 +21,10 @@

<%- __('about.company.title') %>

-

+

<%- __('about.company.description1') %>

-

+

<%- __('about.company.description2') %>

@@ -146,15 +128,12 @@
- <%- include('partials/footer') %> + - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.history/views/about_20251021212532.ejs b/.history/views/about_20251025214158.ejs similarity index 81% rename from .history/views/about_20251021212532.ejs rename to .history/views/about_20251025214158.ejs index 8a673aa..e52df18 100644 --- a/.history/views/about_20251021212532.ejs +++ b/.history/views/about_20251025214158.ejs @@ -1,22 +1,4 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> +
@@ -39,10 +21,10 @@

<%- __('about.company.title') %>

-

+

<%- __('about.company.description1') %>

-

+

<%- __('about.company.description2') %>

@@ -80,7 +62,7 @@

<%- __('about.mission.title') %>

-

+

<%- __('about.mission.description') %>

@@ -146,15 +128,12 @@
- <%- include('partials/footer') %> + - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.history/views/about_20251020225314.ejs b/.history/views/about_20251025214219.ejs similarity index 81% rename from .history/views/about_20251020225314.ejs rename to .history/views/about_20251025214219.ejs index 77a2b0c..e52df18 100644 --- a/.history/views/about_20251020225314.ejs +++ b/.history/views/about_20251025214219.ejs @@ -1,25 +1,7 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> + -
+

@@ -39,10 +21,10 @@

<%- __('about.company.title') %>

-

+

<%- __('about.company.description1') %>

-

+

<%- __('about.company.description2') %>

@@ -80,7 +62,7 @@

<%- __('about.mission.title') %>

-

+

<%- __('about.mission.description') %>

@@ -146,15 +128,12 @@
- <%- include('partials/footer') %> + - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251020225453.ejs b/.history/views/admin/banner-editor_20251020225453.ejs deleted file mode 100644 index d416c48..0000000 --- a/.history/views/admin/banner-editor_20251020225453.ejs +++ /dev/null @@ -1,596 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - -
- - - - -
-
-
-

- - Редактор Баннеров -

-
- - -
-
- - -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251020225526.ejs b/.history/views/admin/banner-editor_20251020225526.ejs deleted file mode 100644 index d416c48..0000000 --- a/.history/views/admin/banner-editor_20251020225526.ejs +++ /dev/null @@ -1,596 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - -
- - - - -
-
-
-

- - Редактор Баннеров -

-
- - -
-
- - -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022193520.ejs b/.history/views/admin/banner-editor_20251022193520.ejs deleted file mode 100644 index 6276981..0000000 --- a/.history/views/admin/banner-editor_20251022193520.ejs +++ /dev/null @@ -1,601 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - -
- - - - -
-
-
-

- - Редактор Баннеров -

-
- - -
-
- - -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022193602.ejs b/.history/views/admin/banner-editor_20251022193602.ejs deleted file mode 100644 index 3f2ba90..0000000 --- a/.history/views/admin/banner-editor_20251022193602.ejs +++ /dev/null @@ -1,652 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Редактор Баннеров -

-

Создание и редактирование баннеров для сайта

-
-
- - -
-
- - -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
- - - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022193627.ejs b/.history/views/admin/banner-editor_20251022193627.ejs deleted file mode 100644 index 3f2ba90..0000000 --- a/.history/views/admin/banner-editor_20251022193627.ejs +++ /dev/null @@ -1,652 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Редактор Баннеров -

-

Создание и редактирование баннеров для сайта

-
-
- - -
-
- - -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
- - - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022193634.ejs b/.history/views/admin/banner-editor_20251022193634.ejs deleted file mode 100644 index 379f7ff..0000000 --- a/.history/views/admin/banner-editor_20251022193634.ejs +++ /dev/null @@ -1,658 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Редактор Баннеров -

-

Создание и редактирование баннеров для сайта

-
- - -
-
-

Инструменты

-
- - -
-
-
- - -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
- - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022193641.ejs b/.history/views/admin/banner-editor_20251022193641.ejs deleted file mode 100644 index 6c09e74..0000000 --- a/.history/views/admin/banner-editor_20251022193641.ejs +++ /dev/null @@ -1,660 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Редактор Баннеров -

-

Создание и редактирование баннеров для сайта

-
- - -
-
-

Инструменты

-
- - -
-
-
- - -
- -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022193752.ejs b/.history/views/admin/banner-editor_20251022193752.ejs deleted file mode 100644 index b0853ff..0000000 --- a/.history/views/admin/banner-editor_20251022193752.ejs +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Редактор Баннеров -

-

Создание и редактирование баннеров для сайта

-
- - -
-
-

Инструменты

-
- - -
-
-
- - -
- -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
-
- - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/banner-editor_20251022194039.ejs b/.history/views/admin/banner-editor_20251022194039.ejs deleted file mode 100644 index b0853ff..0000000 --- a/.history/views/admin/banner-editor_20251022194039.ejs +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - Редактор Баннеров - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Редактор Баннеров -

-

Создание и редактирование баннеров для сайта

-
- - -
-
-

Инструменты

-
- - -
-
-
- - -
- -
-
- -
-
- - -
-

- Текущий баннер: Главная страница -

-
-
-
-

Текущий Баннер

-

Нажмите на изображение ниже, чтобы заменить

-
-
-
-
- -
- -
-
-
- - -
-

- Галерея изображений -

- - - -
- -
- - -
-
-
-
- - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/contacts/list_20251021213012.ejs b/.history/views/admin/contacts/list_20251021213012.ejs deleted file mode 100644 index 430dffd..0000000 --- a/.history/views/admin/contacts/list_20251021213012.ejs +++ /dev/null @@ -1,117 +0,0 @@ - -
-
-
-

- - Управление сообщениями -

-
- - -
-
-
- - - - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/contacts/list_20251021214113.ejs b/.history/views/admin/contacts/list_20251021214113.ejs deleted file mode 100644 index 430dffd..0000000 --- a/.history/views/admin/contacts/list_20251021214113.ejs +++ /dev/null @@ -1,117 +0,0 @@ - -
-
-
-

- - Управление сообщениями -

-
- - -
-
-
- - - - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/contacts/view_20251021213046.ejs b/.history/views/admin/contacts/view_20251021213046.ejs deleted file mode 100644 index f24bee0..0000000 --- a/.history/views/admin/contacts/view_20251021213046.ejs +++ /dev/null @@ -1,219 +0,0 @@ - -
-
-
-

- - Детали сообщения -

- - - Назад к списку - -
-
- -
-
- -
-

Информация о контакте

- -
-
-
Имя
-
<%= contact.name %>
-
- - - - <% if (contact.phone) { %> -
-
Телефон
-
- - <%= contact.phone %> - -
-
- <% } %> - -
-
Дата создания
-
- <%= new Date(contact.createdAt).toLocaleString('ru-RU') %> -
-
-
-
- - -
-

Детали проекта

- -
- <% if (contact.serviceInterest) { %> -
-
Интересующая услуга
-
- - <%= contact.serviceInterest %> - -
-
- <% } %> - - <% if (contact.budget) { %> -
-
Бюджет
-
<%= contact.budget %>
-
- <% } %> - - <% if (contact.timeline) { %> -
-
Временные рамки
-
<%= contact.timeline %>
-
- <% } %> - -
-
Статус
-
- -
-
- -
-
Приоритет
-
- -
-
-
-
-
- - -
-

Сообщение

-
-

<%= contact.message %>

-
-
- - -
- - - - - Ответить по email - - - -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/contacts/view_20251021214113.ejs b/.history/views/admin/contacts/view_20251021214113.ejs deleted file mode 100644 index f24bee0..0000000 --- a/.history/views/admin/contacts/view_20251021214113.ejs +++ /dev/null @@ -1,219 +0,0 @@ - -
-
-
-

- - Детали сообщения -

- - - Назад к списку - -
-
- -
-
- -
-

Информация о контакте

- -
-
-
Имя
-
<%= contact.name %>
-
- - - - <% if (contact.phone) { %> -
-
Телефон
-
- - <%= contact.phone %> - -
-
- <% } %> - -
-
Дата создания
-
- <%= new Date(contact.createdAt).toLocaleString('ru-RU') %> -
-
-
-
- - -
-

Детали проекта

- -
- <% if (contact.serviceInterest) { %> -
-
Интересующая услуга
-
- - <%= contact.serviceInterest %> - -
-
- <% } %> - - <% if (contact.budget) { %> -
-
Бюджет
-
<%= contact.budget %>
-
- <% } %> - - <% if (contact.timeline) { %> -
-
Временные рамки
-
<%= contact.timeline %>
-
- <% } %> - -
-
Статус
-
- -
-
- -
-
Приоритет
-
- -
-
-
-
-
- - -
-

Сообщение

-
-

<%= contact.message %>

-
-
- - -
- - - - - Ответить по email - - - -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020043151.ejs b/.history/views/admin/dashboard_20251020043151.ejs deleted file mode 100644 index a893db9..0000000 --- a/.history/views/admin/dashboard_20251020043151.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - <%= t('admin.dashboard') %> -

-

<%= t('admin.dashboard_subtitle') %>

-
- - -
- -
-
-
-
- -
-
-
-
- <%= t('admin.portfolio_projects') %> -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.services') %> -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.contact_messages') %> -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.users') %> -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- <%= t('admin.recent_portfolio') %> -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044146.ejs b/.history/views/admin/dashboard_20251020044146.ejs deleted file mode 100644 index a893db9..0000000 --- a/.history/views/admin/dashboard_20251020044146.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - <%= t('admin.dashboard') %> -

-

<%= t('admin.dashboard_subtitle') %>

-
- - -
- -
-
-
-
- -
-
-
-
- <%= t('admin.portfolio_projects') %> -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.services') %> -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.contact_messages') %> -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.users') %> -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- <%= t('admin.recent_portfolio') %> -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044644.ejs b/.history/views/admin/dashboard_20251020044644.ejs deleted file mode 100644 index b26e261..0000000 --- a/.history/views/admin/dashboard_20251020044644.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- <%= t('admin.portfolio_projects') %> -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.services') %> -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.contact_messages') %> -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.users') %> -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- <%= t('admin.recent_portfolio') %> -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044653.ejs b/.history/views/admin/dashboard_20251020044653.ejs deleted file mode 100644 index 45164ff..0000000 --- a/.history/views/admin/dashboard_20251020044653.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.services') %> -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.contact_messages') %> -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.users') %> -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- <%= t('admin.recent_portfolio') %> -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044659.ejs b/.history/views/admin/dashboard_20251020044659.ejs deleted file mode 100644 index c4a6f77..0000000 --- a/.history/views/admin/dashboard_20251020044659.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.services') %> -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.contact_messages') %> -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- <%= t('admin.users') %> -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- <%= t('admin.recent_portfolio') %> -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044717.ejs b/.history/views/admin/dashboard_20251020044717.ejs deleted file mode 100644 index 909ca0d..0000000 --- a/.history/views/admin/dashboard_20251020044717.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- <%= t('admin.recent_portfolio') %> -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044727.ejs b/.history/views/admin/dashboard_20251020044727.ejs deleted file mode 100644 index befbb1c..0000000 --- a/.history/views/admin/dashboard_20251020044727.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_portfolio') %>

- <% } %> -
-
- - -
-
-

- <%= t('admin.recent_contacts') %> -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044735.ejs b/.history/views/admin/dashboard_20251020044735.ejs deleted file mode 100644 index 94e8eac..0000000 --- a/.history/views/admin/dashboard_20251020044735.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

<%= t('admin.no_recent_contacts') %>

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044748.ejs b/.history/views/admin/dashboard_20251020044748.ejs deleted file mode 100644 index 22d0b5e..0000000 --- a/.history/views/admin/dashboard_20251020044748.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251020044814.ejs b/.history/views/admin/dashboard_20251020044814.ejs deleted file mode 100644 index 22d0b5e..0000000 --- a/.history/views/admin/dashboard_20251020044814.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<% layout('admin/layout') -%> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022051556.ejs b/.history/views/admin/dashboard_20251022051556.ejs deleted file mode 100644 index d4a4f05..0000000 --- a/.history/views/admin/dashboard_20251022051556.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<%- include('layout', { title: title, user: user }) %> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022051559.ejs b/.history/views/admin/dashboard_20251022051559.ejs deleted file mode 100644 index d4a4f05..0000000 --- a/.history/views/admin/dashboard_20251022051559.ejs +++ /dev/null @@ -1,228 +0,0 @@ -<%- include('layout', { title: title, user: user }) %> - -
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022051633.ejs b/.history/views/admin/dashboard_20251022051633.ejs deleted file mode 100644 index 4be8997..0000000 --- a/.history/views/admin/dashboard_20251022051633.ejs +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022051635.ejs b/.history/views/admin/dashboard_20251022051635.ejs deleted file mode 100644 index 4be8997..0000000 --- a/.history/views/admin/dashboard_20251022051635.ejs +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
\ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022051641.ejs b/.history/views/admin/dashboard_20251022051641.ejs deleted file mode 100644 index db36e51..0000000 --- a/.history/views/admin/dashboard_20251022051641.ejs +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022051755.ejs b/.history/views/admin/dashboard_20251022051755.ejs deleted file mode 100644 index db36e51..0000000 --- a/.history/views/admin/dashboard_20251022051755.ejs +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022052515.ejs b/.history/views/admin/dashboard_20251022052515.ejs deleted file mode 100644 index a72cc3c..0000000 --- a/.history/views/admin/dashboard_20251022052515.ejs +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/dashboard_20251022052523.ejs b/.history/views/admin/dashboard_20251022052523.ejs deleted file mode 100644 index a72cc3c..0000000 --- a/.history/views/admin/dashboard_20251022052523.ejs +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Панель управления -

-

Обзор основных показателей сайта

-
- - -
- -
-
-
-
- -
-
-
-
- Проекты -
-
- <%= stats.portfolioCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Услуги -
-
- <%= stats.servicesCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Сообщения -
-
- <%= stats.contactsCount || 0 %> -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- Пользователи -
-
- <%= stats.usersCount || 0 %> -
-
-
-
-
- -
-
- - -
- -
-
-

- Последние проекты -

-
-
- <% if (recentPortfolio && recentPortfolio.length > 0) { %> -
- <% recentPortfolio.forEach(function(project) { %> -
-
- -
-
-

- <%= project.title %> -

-

- <%= project.category %> -

-
-
- - <%= project.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних проектов

- <% } %> -
-
- - -
-
-

- Последние сообщения -

-
-
- <% if (recentContacts && recentContacts.length > 0) { %> -
- <% recentContacts.forEach(function(contact) { %> -
-
- -
-
-

- <%= contact.name %> -

-

- <%= contact.email %> -

-
-
- - <%= contact.status %> - -
-
- <% }); %> -
- <% } else { %> -

Нет недавних сообщений

- <% } %> -
-
-
- - - -
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/error_20251022050350.ejs b/.history/views/admin/error_20251022050350.ejs deleted file mode 100644 index d044398..0000000 --- a/.history/views/admin/error_20251022050350.ejs +++ /dev/null @@ -1,23 +0,0 @@ -<% layout('admin/layout') -%> - - \ No newline at end of file diff --git a/.history/views/admin/error_20251022050417.ejs b/.history/views/admin/error_20251022050417.ejs deleted file mode 100644 index d044398..0000000 --- a/.history/views/admin/error_20251022050417.ejs +++ /dev/null @@ -1,23 +0,0 @@ -<% layout('admin/layout') -%> - - \ No newline at end of file diff --git a/.history/views/admin/error_20251022051604.ejs b/.history/views/admin/error_20251022051604.ejs deleted file mode 100644 index d238c31..0000000 --- a/.history/views/admin/error_20251022051604.ejs +++ /dev/null @@ -1,23 +0,0 @@ -<%- include('layout', { title: title, user: user }) %> - - \ No newline at end of file diff --git a/.history/views/admin/error_20251022051635.ejs b/.history/views/admin/error_20251022051635.ejs deleted file mode 100644 index d238c31..0000000 --- a/.history/views/admin/error_20251022051635.ejs +++ /dev/null @@ -1,23 +0,0 @@ -<%- include('layout', { title: title, user: user }) %> - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251020043105.ejs b/.history/views/admin/layout_20251020043105.ejs deleted file mode 100644 index bc71cd8..0000000 --- a/.history/views/admin/layout_20251020043105.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - <%= t('site.name') %> Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - <%= t('site.name') %> Admin -

-
-
- - - <%= t('admin.view_site') %> - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251020043142.ejs b/.history/views/admin/layout_20251020043142.ejs deleted file mode 100644 index bc71cd8..0000000 --- a/.history/views/admin/layout_20251020043142.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - <%= t('site.name') %> Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - <%= t('site.name') %> Admin -

-
-
- - - <%= t('admin.view_site') %> - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251020044615.ejs b/.history/views/admin/layout_20251020044615.ejs deleted file mode 100644 index d5fb4f4..0000000 --- a/.history/views/admin/layout_20251020044615.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - <%= t('site.name') %> Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - <%= t('admin.view_site') %> - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251020044623.ejs b/.history/views/admin/layout_20251020044623.ejs deleted file mode 100644 index 4bb97e7..0000000 --- a/.history/views/admin/layout_20251020044623.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - <%= t('site.name') %> Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251020044636.ejs b/.history/views/admin/layout_20251020044636.ejs deleted file mode 100644 index 4fa0570..0000000 --- a/.history/views/admin/layout_20251020044636.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - <%= t('site.name') %> Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251020044814.ejs b/.history/views/admin/layout_20251020044814.ejs deleted file mode 100644 index 4fa0570..0000000 --- a/.history/views/admin/layout_20251020044814.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - <%= t('site.name') %> Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022050302.ejs b/.history/views/admin/layout_20251022050302.ejs deleted file mode 100644 index 9609e49..0000000 --- a/.history/views/admin/layout_20251022050302.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022050311.ejs b/.history/views/admin/layout_20251022050311.ejs deleted file mode 100644 index 9609e49..0000000 --- a/.history/views/admin/layout_20251022050311.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022051612.ejs b/.history/views/admin/layout_20251022051612.ejs deleted file mode 100644 index e21d31a..0000000 --- a/.history/views/admin/layout_20251022051612.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022051635.ejs b/.history/views/admin/layout_20251022051635.ejs deleted file mode 100644 index e21d31a..0000000 --- a/.history/views/admin/layout_20251022051635.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022052659.ejs b/.history/views/admin/layout_20251022052659.ejs deleted file mode 100644 index 9609e49..0000000 --- a/.history/views/admin/layout_20251022052659.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022052711.ejs b/.history/views/admin/layout_20251022052711.ejs deleted file mode 100644 index 151b76f..0000000 --- a/.history/views/admin/layout_20251022052711.ejs +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022052722.ejs b/.history/views/admin/layout_20251022052722.ejs deleted file mode 100644 index b09e7a6..0000000 --- a/.history/views/admin/layout_20251022052722.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022052731.ejs b/.history/views/admin/layout_20251022052731.ejs deleted file mode 100644 index dab51d1..0000000 --- a/.history/views/admin/layout_20251022052731.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/layout_20251022052852.ejs b/.history/views/admin/layout_20251022052852.ejs deleted file mode 100644 index dab51d1..0000000 --- a/.history/views/admin/layout_20251022052852.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020043121.ejs b/.history/views/admin/login_20251020043121.ejs deleted file mode 100644 index 95a1a25..0000000 --- a/.history/views/admin/login_20251020043121.ejs +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - <%= t('admin.login') %> - <%= t('site.name') %> - - - - - - - - - - - - -
-
-
- -
-

- <%= t('admin.login_title') %> -

-

- <%= t('admin.login_subtitle') %> -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020043142.ejs b/.history/views/admin/login_20251020043142.ejs deleted file mode 100644 index 95a1a25..0000000 --- a/.history/views/admin/login_20251020043142.ejs +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - <%= t('admin.login') %> - <%= t('site.name') %> - - - - - - - - - - - - -
-
-
- -
-

- <%= t('admin.login_title') %> -

-

- <%= t('admin.login_subtitle') %> -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020044542.ejs b/.history/views/admin/login_20251020044542.ejs deleted file mode 100644 index 576634c..0000000 --- a/.history/views/admin/login_20251020044542.ejs +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - <%= t('admin.login') %> - <%= t('site.name') %> - - - - - - - - - - - - -
-
-
- -
-

- Вход в админ панель -

-

- Войдите в свой аккаунт для управления сайтом -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020044553.ejs b/.history/views/admin/login_20251020044553.ejs deleted file mode 100644 index 483d894..0000000 --- a/.history/views/admin/login_20251020044553.ejs +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - <%= t('admin.login') %> - <%= t('site.name') %> - - - - - - - - - - - - -
-
-
- -
-

- Вход в админ панель -

-

- Войдите в свой аккаунт для управления сайтом -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020044602.ejs b/.history/views/admin/login_20251020044602.ejs deleted file mode 100644 index 0502977..0000000 --- a/.history/views/admin/login_20251020044602.ejs +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - <%= t('admin.login') %> - <%= t('site.name') %> - - - - - - - - - - - - -
-
-
- -
-

- Вход в админ панель -

-

- Войдите в свой аккаунт для управления сайтом -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020044608.ejs b/.history/views/admin/login_20251020044608.ejs deleted file mode 100644 index ee8f3ba..0000000 --- a/.history/views/admin/login_20251020044608.ejs +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - Вход в админ панель - SmartSolTech - - - - - - - - - - - - -
-
-
- -
-

- Вход в админ панель -

-

- Войдите в свой аккаунт для управления сайтом -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020044813.ejs b/.history/views/admin/login_20251020044813.ejs deleted file mode 100644 index 5509b45..0000000 --- a/.history/views/admin/login_20251020044813.ejs +++ /dev/null @@ -1,81 +0,0 @@ -о - - - - - Вход в админ панель - SmartSolTech - - - - - - - - - - - - -
-
-
- -
-

- Вход в админ панель -

-

- Войдите в свой аккаунт для управления сайтом -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/login_20251020044814.ejs b/.history/views/admin/login_20251020044814.ejs deleted file mode 100644 index 5509b45..0000000 --- a/.history/views/admin/login_20251020044814.ejs +++ /dev/null @@ -1,81 +0,0 @@ -о - - - - - Вход в админ панель - SmartSolTech - - - - - - - - - - - - -
-
-
- -
-

- Вход в админ панель -

-

- Войдите в свой аккаунт для управления сайтом -

-
- -
- <% if (typeof error !== 'undefined') { %> -
- - <%= error %> -
- <% } %> - -
-
- - -
-
- - -
-
- -
- -
- - -
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/media_20251022052001.ejs b/.history/views/admin/media_20251022052001.ejs deleted file mode 100644 index b27d34e..0000000 --- a/.history/views/admin/media_20251022052001.ejs +++ /dev/null @@ -1,848 +0,0 @@ - - - - - - Медиа Галерея - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Медиа Галерея -

-

Управление изображениями и файлами сайта

-
-
- - -
-
-
- - - - - - - - -
-
-
-
- - -
-
- - -
-
-
- -
- - -
-
-
-
- - -
-
-

Файлы

-
- Загрузка... -
- - -
-
-
- - -
-
-

Загрузка медиа файлов...

-
- - - - - -
- -
- - - - - - -
-
-
-
- - - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/media_20251022052056.ejs b/.history/views/admin/media_20251022052056.ejs deleted file mode 100644 index b27d34e..0000000 --- a/.history/views/admin/media_20251022052056.ejs +++ /dev/null @@ -1,848 +0,0 @@ - - - - - - Медиа Галерея - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Медиа Галерея -

-

Управление изображениями и файлами сайта

-
-
- - -
-
-
- - - - - - - - -
-
-
-
- - -
-
- - -
-
-
- -
- - -
-
-
-
- - -
-
-

Файлы

-
- Загрузка... -
- - -
-
-
- - -
-
-

Загрузка медиа файлов...

-
- - - - - -
- -
- - - - - - -
-
-
-
- - - - - - - - - - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251021212914.ejs b/.history/views/admin/portfolio/add_20251021212914.ejs deleted file mode 100644 index 33e70a8..0000000 --- a/.history/views/admin/portfolio/add_20251021212914.ejs +++ /dev/null @@ -1,186 +0,0 @@ - -
-
-

- - Добавить проект -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
- - -
- - -
- - -
- - -
- - -
- -
-
- - - -
- -

или перетащите сюда

-
-

PNG, JPG, WEBP до 10MB

-
-
-
-
- - -
-
- - -
- -
- - -
-
- - -
- - Отмена - - -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251021214113.ejs b/.history/views/admin/portfolio/add_20251021214113.ejs deleted file mode 100644 index 33e70a8..0000000 --- a/.history/views/admin/portfolio/add_20251021214113.ejs +++ /dev/null @@ -1,186 +0,0 @@ - -
-
-

- - Добавить проект -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
- - -
- - -
- - -
- - -
- - -
- -
-
- - - -
- -

или перетащите сюда

-
-

PNG, JPG, WEBP до 10MB

-
-
-
-
- - -
-
- - -
- -
- - -
-
- - -
- - Отмена - - -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251022195336.ejs b/.history/views/admin/portfolio/add_20251022195336.ejs deleted file mode 100644 index 6152e64..0000000 --- a/.history/views/admin/portfolio/add_20251022195336.ejs +++ /dev/null @@ -1,223 +0,0 @@ - -
-
-
-
-
-

- - Добавить новый проект -

-

Заполните информацию о проекте для добавления в портфолио

-
-
- - - - Назад к списку - -
-
-
- -
- -
-
-
-
1
- Основная информация -
-
-
-
2
- Медиа и изображения -
-
-
-
3
- Публикация -
-
-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
- - -
- - -
- - -
- - -
- - -
- -
-
- - - -
- -

или перетащите сюда

-
-

PNG, JPG, WEBP до 10MB

-
-
-
-
- - -
-
- - -
- -
- - -
-
- - -
- - Отмена - - -
- -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251022195407.ejs b/.history/views/admin/portfolio/add_20251022195407.ejs deleted file mode 100644 index 73b92cd..0000000 --- a/.history/views/admin/portfolio/add_20251022195407.ejs +++ /dev/null @@ -1,266 +0,0 @@ - -
-
-
-
-
-

- - Добавить новый проект -

-

Заполните информацию о проекте для добавления в портфолио

-
-
- - - - Назад к списку - -
-
-
- -
- -
-
-
-
1
- Основная информация -
-
-
-
2
- Медиа и изображения -
-
-
-
3
- Публикация -
-
-
- -
- -
-

- - Основная информация о проекте -

- -
- -
- - -

Используйте описательное название, которое четко передает суть проекта

-
- - -
- - -
- Отображается в превью проекта - 0/200 -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
- - -
- - -
- - -
- - -
- - -
- -
-
- - - -
- -

или перетащите сюда

-
-

PNG, JPG, WEBP до 10MB

-
-
-
-
- - -
-
- - -
- -
- - -
-
- - -
- - Отмена - - -
- -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251022195457.ejs b/.history/views/admin/portfolio/add_20251022195457.ejs deleted file mode 100644 index 998b387..0000000 --- a/.history/views/admin/portfolio/add_20251022195457.ejs +++ /dev/null @@ -1,385 +0,0 @@ - -
-
-
-
-
-

- - Добавить новый проект -

-

Заполните информацию о проекте для добавления в портфолио

-
-
- - - - Назад к списку - -
-
-
- -
- -
-
-
-
1
- Основная информация -
-
-
-
2
- Медиа и изображения -
-
-
-
3
- Публикация -
-
-
- -
- -
-

- - Основная информация о проекте -

- -
- -
- - -

Используйте описательное название, которое четко передает суть проекта

-
- - -
- - -
- Отображается в превью проекта - 0/200 -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
- - -
-

- - Описание и технические детали -

- - -
- -
- -

Опишите задачи, решения и результаты проекта

-
- - -
- -
- -
- -
-
-
- - - -
-

Популярные технологии:

-
- <% const popularTechs = ['React', 'Vue.js', 'Node.js', 'Express.js', 'MongoDB', 'PostgreSQL', 'MySQL', 'JavaScript', 'TypeScript', 'HTML5', 'CSS3', 'Tailwind CSS', 'Bootstrap', 'Webpack', 'Docker', 'Git', 'AWS', 'Figma', 'Photoshop']; %> - <% popularTechs.forEach(tech => { %> - - <% }); %> -
-
-
-
- - -
-

- - Изображения и медиа контент -

- -
- -
- -
-
-
- - - -
-
- - или перетащите файлы сюда -
-

- PNG, JPG, WEBP до 10MB каждый. Первое изображение будет использоваться как главное. -

-
-
-
- - - -
-
- - -
-

- - Настройки публикации -

- -
- -
-
-
- -
-
- -

Проект будет виден посетителям сайта

-
-
- -
-
- -
-
- -

Проект будет показан в топе портфолио

-
-
-
- - -
-
SEO настройки (необязательно)
-
-
- - -

Рекомендуется до 60 символов

-
- -
- - -

Рекомендуется до 160 символов

-
-
-
-
-
- - -
-
- -
- -
- - Отмена - - - -
-
- -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251022195641.ejs b/.history/views/admin/portfolio/add_20251022195641.ejs deleted file mode 100644 index 6032e4d..0000000 --- a/.history/views/admin/portfolio/add_20251022195641.ejs +++ /dev/null @@ -1,776 +0,0 @@ - -
-
-
-
-
-

- - Добавить новый проект -

-

Заполните информацию о проекте для добавления в портфолио

-
-
- - - - Назад к списку - -
-
-
- -
- -
-
-
-
1
- Основная информация -
-
-
-
2
- Медиа и изображения -
-
-
-
3
- Публикация -
-
-
- -
- -
-

- - Основная информация о проекте -

- -
- -
- - -

Используйте описательное название, которое четко передает суть проекта

-
- - -
- - -
- Отображается в превью проекта - 0/200 -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
- - -
-

- - Описание и технические детали -

- - -
- -
- -

Опишите задачи, решения и результаты проекта

-
- - -
- -
- -
- -
-
-
- - - -
-

Популярные технологии:

-
- <% const popularTechs = ['React', 'Vue.js', 'Node.js', 'Express.js', 'MongoDB', 'PostgreSQL', 'MySQL', 'JavaScript', 'TypeScript', 'HTML5', 'CSS3', 'Tailwind CSS', 'Bootstrap', 'Webpack', 'Docker', 'Git', 'AWS', 'Figma', 'Photoshop']; %> - <% popularTechs.forEach(tech => { %> - - <% }); %> -
-
-
-
- - -
-

- - Изображения и медиа контент -

- -
- -
- -
-
-
- - - -
-
- - или перетащите файлы сюда -
-

- PNG, JPG, WEBP до 10MB каждый. Первое изображение будет использоваться как главное. -

-
-
-
- - - -
-
- - -
-

- - Настройки публикации -

- -
- -
-
-
- -
-
- -

Проект будет виден посетителям сайта

-
-
- -
-
- -
-
- -

Проект будет показан в топе портфолио

-
-
-
- - -
-
SEO настройки (необязательно)
-
-
- - -

Рекомендуется до 60 символов

-
- -
- - -

Рекомендуется до 160 символов

-
-
-
-
-
- - -
-
- -
- -
- - Отмена - - - -
-
- -
- - - - - - \ No newline at end of file diff --git a/.history/views/admin/portfolio/add_20251022195905.ejs b/.history/views/admin/portfolio/add_20251022195905.ejs deleted file mode 100644 index 6032e4d..0000000 --- a/.history/views/admin/portfolio/add_20251022195905.ejs +++ /dev/null @@ -1,776 +0,0 @@ - -
-
-
-
-
-

- - Добавить новый проект -

-

Заполните информацию о проекте для добавления в портфолио

-
-
- - - - Назад к списку - -
-
-
- -
- -
-
-
-
1
- Основная информация -
-
-
-
2
- Медиа и изображения -
-
-
-
3
- Публикация -
-
-
- -
- -
-

- - Основная информация о проекте -

- -
- -
- - -

Используйте описательное название, которое четко передает суть проекта

-
- - -
- - -
- Отображается в превью проекта - 0/200 -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
- - -
-

- - Описание и технические детали -

- - -
- -
- -

Опишите задачи, решения и результаты проекта

-
- - -
- -
- -
- -
-
-
- - - -
-

Популярные технологии:

-
- <% const popularTechs = ['React', 'Vue.js', 'Node.js', 'Express.js', 'MongoDB', 'PostgreSQL', 'MySQL', 'JavaScript', 'TypeScript', 'HTML5', 'CSS3', 'Tailwind CSS', 'Bootstrap', 'Webpack', 'Docker', 'Git', 'AWS', 'Figma', 'Photoshop']; %> - <% popularTechs.forEach(tech => { %> - - <% }); %> -
-
-
-
- - -
-

- - Изображения и медиа контент -

- -
- -
- -
-
-
- - - -
-
- - или перетащите файлы сюда -
-

- PNG, JPG, WEBP до 10MB каждый. Первое изображение будет использоваться как главное. -

-
-
-
- - - -
-
- - -
-

- - Настройки публикации -

- -
- -
-
-
- -
-
- -

Проект будет виден посетителям сайта

-
-
- -
-
- -
-
- -

Проект будет показан в топе портфолио

-
-
-
- - -
-
SEO настройки (необязательно)
-
-
- - -

Рекомендуется до 60 символов

-
- -
- - -

Рекомендуется до 160 символов

-
-
-
-
-
- - -
-
- -
- -
- - Отмена - - - -
-
- -
- - - - - - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251021212843.ejs b/.history/views/admin/portfolio/list_20251021212843.ejs deleted file mode 100644 index b54e8e7..0000000 --- a/.history/views/admin/portfolio/list_20251021212843.ejs +++ /dev/null @@ -1,123 +0,0 @@ - -
-
-
-

- - Управление портфолио -

- - - Добавить проект - -
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -
    <%= item.title %>
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - <% if (!item.isPublished) { %> - - Черновик - - <% } %> -
    -
    - <%= item.category %> • - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    -
    -
    -
    - - - - - - - -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251021214113.ejs b/.history/views/admin/portfolio/list_20251021214113.ejs deleted file mode 100644 index b54e8e7..0000000 --- a/.history/views/admin/portfolio/list_20251021214113.ejs +++ /dev/null @@ -1,123 +0,0 @@ - -
-
-
-

- - Управление портфолио -

- - - Добавить проект - -
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -
    <%= item.title %>
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - <% if (!item.isPublished) { %> - - Черновик - - <% } %> -
    -
    - <%= item.category %> • - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    -
    -
    -
    - - - - - - - -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251022195202.ejs b/.history/views/admin/portfolio/list_20251022195202.ejs deleted file mode 100644 index bd7f666..0000000 --- a/.history/views/admin/portfolio/list_20251022195202.ejs +++ /dev/null @@ -1,147 +0,0 @@ - -
-
-
-
-

- - Управление портфолио -

-

- Всего проектов: <%= portfolio ? portfolio.length : 0 %> -

-
-
-
- - -
- - - - Добавить проект - -
-
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -
    <%= item.title %>
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - <% if (!item.isPublished) { %> - - Черновик - - <% } %> -
    -
    - <%= item.category %> • - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    -
    -
    -
    - - - - - - - -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251022195234.ejs b/.history/views/admin/portfolio/list_20251022195234.ejs deleted file mode 100644 index c556764..0000000 --- a/.history/views/admin/portfolio/list_20251022195234.ejs +++ /dev/null @@ -1,186 +0,0 @@ - -
-
-
-
-

- - Управление портфолио -

-

- Всего проектов: <%= portfolio ? portfolio.length : 0 %> -

-
-
-
- - -
- - - - Добавить проект - -
-
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -

    <%= item.title %>

    -
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - - - <%= item.isPublished ? 'Опубликовано' : 'Черновик' %> - -
    -
    -

    <%= item.shortDescription || 'Описание не указано' %>

    -
    -
    - - <%= item.category.replace('-', ' ') %> -
    -
    - - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    - <% if (item.viewCount && item.viewCount > 0) { %> -
    - - <%= item.viewCount %> просмотров -
    - <% } %> - <% if (item.technologies && item.technologies.length > 0) { %> -
    - - <%= item.technologies.slice(0, 2).join(', ') %><%= item.technologies.length > 2 ? '...' : '' %> -
    - <% } %> -
    -
    -
    -
    - <% if (item.isPublished) { %> - - - - <% } %> - - - - - - -
    -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251022195245.ejs b/.history/views/admin/portfolio/list_20251022195245.ejs deleted file mode 100644 index b4386d2..0000000 --- a/.history/views/admin/portfolio/list_20251022195245.ejs +++ /dev/null @@ -1,186 +0,0 @@ - -
-
-
-
-

- - Управление портфолио -

-

- Всего проектов: <%= portfolio ? portfolio.length : 0 %> -

-
-
-
- - -
- - - - Добавить проект - -
-
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -

    <%= item.title %>

    -
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - - - <%= item.isPublished ? 'Опубликовано' : 'Черновик' %> - -
    -
    -

    <%= item.shortDescription || 'Описание не указано' %>

    -
    -
    - - <%= item.category.replace('-', ' ') %> -
    -
    - - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    - <% if (item.viewCount && item.viewCount > 0) { %> -
    - - <%= item.viewCount %> просмотров -
    - <% } %> - <% if (item.technologies && item.technologies.length > 0) { %> -
    - - <%= item.technologies.slice(0, 2).join(', ') %><%= item.technologies.length > 2 ? '...' : '' %> -
    - <% } %> -
    -
    -
    -
    - <% if (item.isPublished) { %> - - - - <% } %> - - - - - - -
    -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251022195313.ejs b/.history/views/admin/portfolio/list_20251022195313.ejs deleted file mode 100644 index 7c7031a..0000000 --- a/.history/views/admin/portfolio/list_20251022195313.ejs +++ /dev/null @@ -1,358 +0,0 @@ - -
-
-
-
-

- - Управление портфолио -

-

- Всего проектов: <%= portfolio ? portfolio.length : 0 %> -

-
-
-
- - -
- - - - Добавить проект - -
-
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -

    <%= item.title %>

    -
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - - - <%= item.isPublished ? 'Опубликовано' : 'Черновик' %> - -
    -
    -

    <%= item.shortDescription || 'Описание не указано' %>

    -
    -
    - - <%= item.category.replace('-', ' ') %> -
    -
    - - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    - <% if (item.viewCount && item.viewCount > 0) { %> -
    - - <%= item.viewCount %> просмотров -
    - <% } %> - <% if (item.technologies && item.technologies.length > 0) { %> -
    - - <%= item.technologies.slice(0, 2).join(', ') %><%= item.technologies.length > 2 ? '...' : '' %> -
    - <% } %> -
    -
    -
    -
    - <% if (item.isPublished) { %> - - - - <% } %> - - - - - - -
    -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/portfolio/list_20251022195905.ejs b/.history/views/admin/portfolio/list_20251022195905.ejs deleted file mode 100644 index 7c7031a..0000000 --- a/.history/views/admin/portfolio/list_20251022195905.ejs +++ /dev/null @@ -1,358 +0,0 @@ - -
-
-
-
-

- - Управление портфолио -

-

- Всего проектов: <%= portfolio ? portfolio.length : 0 %> -

-
-
-
- - -
- - - - Добавить проект - -
-
-
- -
-
    - <% if (portfolio && portfolio.length > 0) { %> - <% portfolio.forEach(item => { %> -
  • -
    -
    -
    -
    - <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
    - -
    - <% } %> -
    -
    -
    -

    <%= item.title %>

    -
    - <% if (item.featured) { %> - - - Рекомендуемое - - <% } %> - - - <%= item.isPublished ? 'Опубликовано' : 'Черновик' %> - -
    -
    -

    <%= item.shortDescription || 'Описание не указано' %>

    -
    -
    - - <%= item.category.replace('-', ' ') %> -
    -
    - - <%= new Date(item.createdAt).toLocaleDateString('ru-RU') %> -
    - <% if (item.viewCount && item.viewCount > 0) { %> -
    - - <%= item.viewCount %> просмотров -
    - <% } %> - <% if (item.technologies && item.technologies.length > 0) { %> -
    - - <%= item.technologies.slice(0, 2).join(', ') %><%= item.technologies.length > 2 ? '...' : '' %> -
    - <% } %> -
    -
    -
    -
    - <% if (item.isPublished) { %> - - - - <% } %> - - - - - - -
    -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Проекты не найдены

    - - Добавить первый проект - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/services/list_20251021212943.ejs b/.history/views/admin/services/list_20251021212943.ejs deleted file mode 100644 index 5d2add4..0000000 --- a/.history/views/admin/services/list_20251021212943.ejs +++ /dev/null @@ -1,121 +0,0 @@ - -
-
-
-

- - Управление услугами -

- - - Добавить услугу - -
-
- -
-
    - <% if (services && services.length > 0) { %> - <% services.forEach(service => { %> -
  • -
    -
    -
    -
    - -
    -
    -
    -
    -
    <%= service.name %>
    - <% if (service.featured) { %> - - - Рекомендуемая - - <% } %> - <% if (!service.isActive) { %> - - Неактивна - - <% } %> -
    -
    - <%= service.category %> • - <% if (service.pricing && service.pricing.basePrice) { %> - от $<%= service.pricing.basePrice %> - <% } %> -
    -
    -
    -
    - - - - - - - -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Услуги не найдены

    - - Добавить первую услугу - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/services/list_20251021214113.ejs b/.history/views/admin/services/list_20251021214113.ejs deleted file mode 100644 index 5d2add4..0000000 --- a/.history/views/admin/services/list_20251021214113.ejs +++ /dev/null @@ -1,121 +0,0 @@ - -
-
-
-

- - Управление услугами -

- - - Добавить услугу - -
-
- -
-
    - <% if (services && services.length > 0) { %> - <% services.forEach(service => { %> -
  • -
    -
    -
    -
    - -
    -
    -
    -
    -
    <%= service.name %>
    - <% if (service.featured) { %> - - - Рекомендуемая - - <% } %> - <% if (!service.isActive) { %> - - Неактивна - - <% } %> -
    -
    - <%= service.category %> • - <% if (service.pricing && service.pricing.basePrice) { %> - от $<%= service.pricing.basePrice %> - <% } %> -
    -
    -
    -
    - - - - - - - -
    -
    -
  • - <% }) %> - <% } else { %> -
  • -
    - -

    Услуги не найдены

    - - Добавить первую услугу - -
    -
  • - <% } %> -
-
- - - <% if (pagination && pagination.total > 1) { %> -
-
- <% if (pagination.hasPrev) { %> - - Предыдущая - - <% } %> - <% if (pagination.hasNext) { %> - - Следующая - - <% } %> -
-
- <% } %> -
- - \ No newline at end of file diff --git a/.history/views/admin/settings_20251021213351.ejs b/.history/views/admin/settings_20251021213351.ejs deleted file mode 100644 index 61ea517..0000000 --- a/.history/views/admin/settings_20251021213351.ejs +++ /dev/null @@ -1,242 +0,0 @@ - -
-
-

- - Настройки сайта -

-
- -
-
- -
-

Основные настройки

-
-
- - -
- -
- - -
- -
- - - <% if (settings.logo) { %> - Current logo - <% } %> -
- -
- - - <% if (settings.favicon) { %> - Current favicon - <% } %> -
-
-
- - -
-

Контактная информация

-
-
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Социальные сети

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Telegram Bot

-
-
- - -

Получите токен у @BotFather

-
- -
- - -

ID чата для уведомлений

-
- -
- -
-
-
-
- - -
-

SEO настройки

-
-
- - -
- -
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/settings_20251021214113.ejs b/.history/views/admin/settings_20251021214113.ejs deleted file mode 100644 index 61ea517..0000000 --- a/.history/views/admin/settings_20251021214113.ejs +++ /dev/null @@ -1,242 +0,0 @@ - -
-
-

- - Настройки сайта -

-
- -
-
- -
-

Основные настройки

-
-
- - -
- -
- - -
- -
- - - <% if (settings.logo) { %> - Current logo - <% } %> -
- -
- - - <% if (settings.favicon) { %> - Current favicon - <% } %> -
-
-
- - -
-

Контактная информация

-
-
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Социальные сети

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Telegram Bot

-
-
- - -

Получите токен у @BotFather

-
- -
- - -

ID чата для уведомлений

-
- -
- -
-
-
-
- - -
-

SEO настройки

-
-
- - -
- -
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/settings_20251022052811.ejs b/.history/views/admin/settings_20251022052811.ejs deleted file mode 100644 index 15885e1..0000000 --- a/.history/views/admin/settings_20251022052811.ejs +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - Настройки сайта - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Настройки сайта -

-

Управление основными параметрами сайта

-
- - -
-
-

- - Настройки сайта -

-
- -
-
- -
-

Основные настройки

-
-
- - -
- -
- - -
- -
- - - <% if (settings.logo) { %> - Current logo - <% } %> -
- -
- - - <% if (settings.favicon) { %> - Current favicon - <% } %> -
-
-
- - -
-

Контактная информация

-
-
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Социальные сети

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Telegram Bot

-
-
- - -

Получите токен у @BotFather

-
- -
- - -

ID чата для уведомлений

-
- -
- -
-
-
-
- - -
-

SEO настройки

-
-
- - -
- -
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - \ No newline at end of file diff --git a/.history/views/admin/settings_20251022052834.ejs b/.history/views/admin/settings_20251022052834.ejs deleted file mode 100644 index 581ae50..0000000 --- a/.history/views/admin/settings_20251022052834.ejs +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Настройки сайта - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Настройки сайта -

-

Управление основными параметрами сайта

-
- - -
-
-

- - Настройки сайта -

-
- -
-
- -
-

Основные настройки

-
-
- - -
- -
- - -
- -
- - - <% if (settings.logo) { %> - Current logo - <% } %> -
- -
- - - <% if (settings.favicon) { %> - Current favicon - <% } %> -
-
-
- - -
-

Контактная информация

-
-
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Социальные сети

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Telegram Bot

-
-
- - -

Получите токен у @BotFather

-
- -
- - -

ID чата для уведомлений

-
- -
- -
-
-
-
- - -
-

SEO настройки

-
-
- - -
- -
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - -
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/settings_20251022052852.ejs b/.history/views/admin/settings_20251022052852.ejs deleted file mode 100644 index 581ae50..0000000 --- a/.history/views/admin/settings_20251022052852.ejs +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Настройки сайта - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-

- - Настройки сайта -

-

Управление основными параметрами сайта

-
- - -
-
-

- - Настройки сайта -

-
- -
-
- -
-

Основные настройки

-
-
- - -
- -
- - -
- -
- - - <% if (settings.logo) { %> - Current logo - <% } %> -
- -
- - - <% if (settings.favicon) { %> - Current favicon - <% } %> -
-
-
- - -
-

Контактная информация

-
-
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Социальные сети

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- - -
-

Telegram Bot

-
-
- - -

Получите токен у @BotFather

-
- -
- - -

ID чата для уведомлений

-
- -
- -
-
-
-
- - -
-

SEO настройки

-
-
- - -
- -
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - -
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/admin/telegram_20251022052503.ejs b/.history/views/admin/telegram_20251022052503.ejs deleted file mode 100644 index a3226bc..0000000 --- a/.history/views/admin/telegram_20251022052503.ejs +++ /dev/null @@ -1,347 +0,0 @@ - - - - - - Telegram Bot - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Telegram Bot -

-

Настройка и управление уведомлениями через Telegram

-
-
-
-
- - <%= botConfigured ? 'Подключен' : 'Не настроен' %> - -
-
-
-
- - <% if (!botConfigured) { %> - -
-
-
-

Настройка Telegram бота

-
-

Для настройки Telegram бота выполните следующие шаги:

-
    -
  1. Создайте бота через @BotFather в Telegram
  2. -
  3. Получите токен бота от BotFather
  4. -
  5. Создайте группу или используйте личный чат для получения уведомлений
  6. -
  7. Получите Chat ID группы или чата
  8. -
  9. Добавьте в файл .env следующие переменные:
  10. -
-
- TELEGRAM_BOT_TOKEN=your_bot_token_here
- TELEGRAM_CHAT_ID=your_chat_id_here -
-

После добавления переменных перезапустите сервер.

-
-
-
-
- <% } else { %> - -
- -
-

- - Проверка подключения -

-

- Отправить тестовое сообщение для проверки работоспособности бота. -

- - -
- - -
-

- - Отправить сообщение -

-
-
- - -
- -
- -
-
- - -
-

- - Настройки уведомлений -

- -
- -
-

Типы уведомлений

-
-
-
- - Новые обращения -
-
-
-
-
- - Расчеты стоимости -
-
-
-
-
- - Новые проекты -
-
-
-
-
- - Новые услуги -
-
-
-
-
- - -
-

Информация о боте

-
-
- Статус: - Активен -
-
- Токен: - •••••••••• -
-
- Chat ID: - •••••••••• -
-
- Последнее уведомление: - Недавно -
-
-
-
-
- - -
-

- - Недавние уведомления -

- -
- -

Уведомления будут отображаться здесь после отправки

-
-
- <% } %> -
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/telegram_20251022052523.ejs b/.history/views/admin/telegram_20251022052523.ejs deleted file mode 100644 index a3226bc..0000000 --- a/.history/views/admin/telegram_20251022052523.ejs +++ /dev/null @@ -1,347 +0,0 @@ - - - - - - Telegram Bot - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Telegram Bot -

-

Настройка и управление уведомлениями через Telegram

-
-
-
-
- - <%= botConfigured ? 'Подключен' : 'Не настроен' %> - -
-
-
-
- - <% if (!botConfigured) { %> - -
-
-
-

Настройка Telegram бота

-
-

Для настройки Telegram бота выполните следующие шаги:

-
    -
  1. Создайте бота через @BotFather в Telegram
  2. -
  3. Получите токен бота от BotFather
  4. -
  5. Создайте группу или используйте личный чат для получения уведомлений
  6. -
  7. Получите Chat ID группы или чата
  8. -
  9. Добавьте в файл .env следующие переменные:
  10. -
-
- TELEGRAM_BOT_TOKEN=your_bot_token_here
- TELEGRAM_CHAT_ID=your_chat_id_here -
-

После добавления переменных перезапустите сервер.

-
-
-
-
- <% } else { %> - -
- -
-

- - Проверка подключения -

-

- Отправить тестовое сообщение для проверки работоспособности бота. -

- - -
- - -
-

- - Отправить сообщение -

-
-
- - -
- -
- -
-
- - -
-

- - Настройки уведомлений -

- -
- -
-

Типы уведомлений

-
-
-
- - Новые обращения -
-
-
-
-
- - Расчеты стоимости -
-
-
-
-
- - Новые проекты -
-
-
-
-
- - Новые услуги -
-
-
-
-
- - -
-

Информация о боте

-
-
- Статус: - Активен -
-
- Токен: - •••••••••• -
-
- Chat ID: - •••••••••• -
-
- Последнее уведомление: - Недавно -
-
-
-
-
- - -
-

- - Недавние уведомления -

- -
- -

Уведомления будут отображаться здесь после отправки

-
-
- <% } %> -
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/telegram_20251022195740.ejs b/.history/views/admin/telegram_20251022195740.ejs deleted file mode 100644 index e1b424c..0000000 --- a/.history/views/admin/telegram_20251022195740.ejs +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - Telegram Bot - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Telegram Bot -

-

Настройка и управление уведомлениями через Telegram

-
-
-
-
- - <%= botConfigured ? 'Подключен' : 'Не настроен' %> - -
-
-
-
- - -
-

- - Конфигурация бота -

- -
-
- -
- -
- - -
-

- Получите токен от @BotFather -

-
- - -
- - -

- Оставьте пустым, если будете выбирать чат из списка -

-
-
- -
- - -
-
- - -
- - <% if (botConfigured) { %> - -
-
-

- - Информация о боте -

- -
- -
- <% if (botInfo) { %> -
-
-
Имя бота
-
@<%= botInfo.username %>
-
-
-
Отображаемое имя
-
<%= botInfo.first_name %>
-
-
-
ID бота
-
<%= botInfo.id %>
-
-
-
Может читать сообщения
-
- <%= botInfo.can_read_all_group_messages ? 'Да' : 'Нет' %> -
-
-
- <% } else { %> -
- -

Настройте токен бота для получения информации

-
- <% } %> -
-
- - -
-
-

- - Доступные чаты -

- -
- -
- <% if (availableChats && availableChats.length > 0) { %> -
- <% availableChats.forEach(chat => { %> -
-
-
- -
-
-
<%= chat.title %>
-
- <%= chat.type %> • ID: <%= chat.id %> - <% if (chat.username) { %>• @<%= chat.username %><% } %> -
-
-
- -
- <% }); %> -
- <% } else { %> -
- -

Чаты не найдены

-

Отправьте боту сообщение или добавьте его в группу, затем нажмите "Найти чаты"

-
- <% } %> -
-
- <% } %> - - -
-

- - Отправить сообщение -

- -
- -
- - -
- - -
- -
- <% if (availableChats && availableChats.length > 0) { %> - <% availableChats.forEach(chat => { %> - - <% }); %> - <% } else { %> -
- - Сообщение будет отправлено в чат по умолчанию -
- <% } %> -
-
- - -
-

Настройки сообщения

-
- - -
-
- - -
-
- - -
- -
- - -
-
-
- - -
- -
- -
-

- - Проверка подключения -

-

- Отправить тестовое сообщение для проверки работоспособности бота. -

- - -
- - -
-

- - Отправить сообщение -

-
-
- - -
- -
- -
-
- - -
-

- - Настройки уведомлений -

- -
- -
-

Типы уведомлений

-
-
-
- - Новые обращения -
-
-
-
-
- - Расчеты стоимости -
-
-
-
-
- - Новые проекты -
-
-
-
-
- - Новые услуги -
-
-
-
-
- - -
-

Информация о боте

-
-
- Статус: - Активен -
-
- Токен: - •••••••••• -
-
- Chat ID: - •••••••••• -
-
- Последнее уведомление: - Недавно -
-
-
-
-
- - -
-

- - Недавние уведомления -

- -
- -

Уведомления будут отображаться здесь после отправки

-
-
- <% } %> -
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/telegram_20251022195839.ejs b/.history/views/admin/telegram_20251022195839.ejs deleted file mode 100644 index 5a53154..0000000 --- a/.history/views/admin/telegram_20251022195839.ejs +++ /dev/null @@ -1,885 +0,0 @@ - - - - - - Telegram Bot - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Telegram Bot -

-

Настройка и управление уведомлениями через Telegram

-
-
-
-
- - <%= botConfigured ? 'Подключен' : 'Не настроен' %> - -
-
-
-
- - -
-

- - Конфигурация бота -

- -
-
- -
- -
- - -
-

- Получите токен от @BotFather -

-
- - -
- - -

- Оставьте пустым, если будете выбирать чат из списка -

-
-
- -
- - -
-
- - -
- - <% if (botConfigured) { %> - -
-
-

- - Информация о боте -

- -
- -
- <% if (botInfo) { %> -
-
-
Имя бота
-
@<%= botInfo.username %>
-
-
-
Отображаемое имя
-
<%= botInfo.first_name %>
-
-
-
ID бота
-
<%= botInfo.id %>
-
-
-
Может читать сообщения
-
- <%= botInfo.can_read_all_group_messages ? 'Да' : 'Нет' %> -
-
-
- <% } else { %> -
- -

Настройте токен бота для получения информации

-
- <% } %> -
-
- - -
-
-

- - Доступные чаты -

- -
- -
- <% if (availableChats && availableChats.length > 0) { %> -
- <% availableChats.forEach(chat => { %> -
-
-
- -
-
-
<%= chat.title %>
-
- <%= chat.type %> • ID: <%= chat.id %> - <% if (chat.username) { %>• @<%= chat.username %><% } %> -
-
-
- -
- <% }); %> -
- <% } else { %> -
- -

Чаты не найдены

-

Отправьте боту сообщение или добавьте его в группу, затем нажмите "Найти чаты"

-
- <% } %> -
-
- <% } %> - - -
-

- - Отправить сообщение -

- -
- -
- - -
- - -
- -
- <% if (availableChats && availableChats.length > 0) { %> - <% availableChats.forEach(chat => { %> - - <% }); %> - <% } else { %> -
- - Сообщение будет отправлено в чат по умолчанию -
- <% } %> -
-
- - -
-

Настройки сообщения

-
- - -
-
- - -
-
- - -
- -
- - -
-
-
- - -
- -
- -
-

- - Проверка подключения -

-

- Отправить тестовое сообщение для проверки работоспособности бота. -

- - -
- - -
-

- - Отправить сообщение -

-
-
- - -
- -
- -
-
- - -
-

- - Настройки уведомлений -

- -
- -
-

Типы уведомлений

-
-
-
- - Новые обращения -
-
-
-
-
- - Расчеты стоимости -
-
-
-
-
- - Новые проекты -
-
-
-
-
- - Новые услуги -
-
-
-
-
- - -
-

Информация о боте

-
-
- Статус: - Активен -
-
- Токен: - •••••••••• -
-
- Chat ID: - •••••••••• -
-
- Последнее уведомление: - Недавно -
-
-
-
-
- - -
-

- - Недавние уведомления -

- -
- -

Уведомления будут отображаться здесь после отправки

-
-
- <% } %> -
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/admin/telegram_20251022195905.ejs b/.history/views/admin/telegram_20251022195905.ejs deleted file mode 100644 index 5a53154..0000000 --- a/.history/views/admin/telegram_20251022195905.ejs +++ /dev/null @@ -1,885 +0,0 @@ - - - - - - Telegram Bot - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- -
- - - - -
-
- -
-
-
-

- - Telegram Bot -

-

Настройка и управление уведомлениями через Telegram

-
-
-
-
- - <%= botConfigured ? 'Подключен' : 'Не настроен' %> - -
-
-
-
- - -
-

- - Конфигурация бота -

- -
-
- -
- -
- - -
-

- Получите токен от @BotFather -

-
- - -
- - -

- Оставьте пустым, если будете выбирать чат из списка -

-
-
- -
- - -
-
- - -
- - <% if (botConfigured) { %> - -
-
-

- - Информация о боте -

- -
- -
- <% if (botInfo) { %> -
-
-
Имя бота
-
@<%= botInfo.username %>
-
-
-
Отображаемое имя
-
<%= botInfo.first_name %>
-
-
-
ID бота
-
<%= botInfo.id %>
-
-
-
Может читать сообщения
-
- <%= botInfo.can_read_all_group_messages ? 'Да' : 'Нет' %> -
-
-
- <% } else { %> -
- -

Настройте токен бота для получения информации

-
- <% } %> -
-
- - -
-
-

- - Доступные чаты -

- -
- -
- <% if (availableChats && availableChats.length > 0) { %> -
- <% availableChats.forEach(chat => { %> -
-
-
- -
-
-
<%= chat.title %>
-
- <%= chat.type %> • ID: <%= chat.id %> - <% if (chat.username) { %>• @<%= chat.username %><% } %> -
-
-
- -
- <% }); %> -
- <% } else { %> -
- -

Чаты не найдены

-

Отправьте боту сообщение или добавьте его в группу, затем нажмите "Найти чаты"

-
- <% } %> -
-
- <% } %> - - -
-

- - Отправить сообщение -

- -
- -
- - -
- - -
- -
- <% if (availableChats && availableChats.length > 0) { %> - <% availableChats.forEach(chat => { %> - - <% }); %> - <% } else { %> -
- - Сообщение будет отправлено в чат по умолчанию -
- <% } %> -
-
- - -
-

Настройки сообщения

-
- - -
-
- - -
-
- - -
- -
- - -
-
-
- - -
- -
- -
-

- - Проверка подключения -

-

- Отправить тестовое сообщение для проверки работоспособности бота. -

- - -
- - -
-

- - Отправить сообщение -

-
-
- - -
- -
- -
-
- - -
-

- - Настройки уведомлений -

- -
- -
-

Типы уведомлений

-
-
-
- - Новые обращения -
-
-
-
-
- - Расчеты стоимости -
-
-
-
-
- - Новые проекты -
-
-
-
-
- - Новые услуги -
-
-
-
-
- - -
-

Информация о боте

-
-
- Статус: - Активен -
-
- Токен: - •••••••••• -
-
- Chat ID: - •••••••••• -
-
- Последнее уведомление: - Недавно -
-
-
-
-
- - -
-

- - Недавние уведомления -

- -
- -

Уведомления будут отображаться здесь после отправки

-
-
- <% } %> -
-
-
- - - - - - - \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213102.ejs b/.history/views/calculator-modern_20251025213102.ejs new file mode 100644 index 0000000..8692591 --- /dev/null +++ b/.history/views/calculator-modern_20251025213102.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213109.ejs b/.history/views/calculator-modern_20251025213109.ejs new file mode 100644 index 0000000..9b61078 --- /dev/null +++ b/.history/views/calculator-modern_20251025213109.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213115.ejs b/.history/views/calculator-modern_20251025213115.ejs new file mode 100644 index 0000000..80f740c --- /dev/null +++ b/.history/views/calculator-modern_20251025213115.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213121.ejs b/.history/views/calculator-modern_20251025213121.ejs new file mode 100644 index 0000000..edd20c6 --- /dev/null +++ b/.history/views/calculator-modern_20251025213121.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213128.ejs b/.history/views/calculator-modern_20251025213128.ejs new file mode 100644 index 0000000..7e235bf --- /dev/null +++ b/.history/views/calculator-modern_20251025213128.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213135.ejs b/.history/views/calculator-modern_20251025213135.ejs new file mode 100644 index 0000000..dac192c --- /dev/null +++ b/.history/views/calculator-modern_20251025213135.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213149.ejs b/.history/views/calculator-modern_20251025213149.ejs new file mode 100644 index 0000000..39da390 --- /dev/null +++ b/.history/views/calculator-modern_20251025213149.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213155.ejs b/.history/views/calculator-modern_20251025213155.ejs new file mode 100644 index 0000000..b411560 --- /dev/null +++ b/.history/views/calculator-modern_20251025213155.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213202.ejs b/.history/views/calculator-modern_20251025213202.ejs new file mode 100644 index 0000000..2936d27 --- /dev/null +++ b/.history/views/calculator-modern_20251025213202.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213208.ejs b/.history/views/calculator-modern_20251025213208.ejs new file mode 100644 index 0000000..9e2181b --- /dev/null +++ b/.history/views/calculator-modern_20251025213208.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213215.ejs b/.history/views/calculator-modern_20251025213215.ejs new file mode 100644 index 0000000..0e9c5cb --- /dev/null +++ b/.history/views/calculator-modern_20251025213215.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213232.ejs b/.history/views/calculator-modern_20251025213232.ejs new file mode 100644 index 0000000..220be71 --- /dev/null +++ b/.history/views/calculator-modern_20251025213232.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213243.ejs b/.history/views/calculator-modern_20251025213243.ejs new file mode 100644 index 0000000..ba7724f --- /dev/null +++ b/.history/views/calculator-modern_20251025213243.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213250.ejs b/.history/views/calculator-modern_20251025213250.ejs new file mode 100644 index 0000000..ec58885 --- /dev/null +++ b/.history/views/calculator-modern_20251025213250.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213256.ejs b/.history/views/calculator-modern_20251025213256.ejs new file mode 100644 index 0000000..82909cc --- /dev/null +++ b/.history/views/calculator-modern_20251025213256.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213303.ejs b/.history/views/calculator-modern_20251025213303.ejs new file mode 100644 index 0000000..35a5742 --- /dev/null +++ b/.history/views/calculator-modern_20251025213303.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251025213357.ejs b/.history/views/calculator-modern_20251025213357.ejs new file mode 100644 index 0000000..35a5742 --- /dev/null +++ b/.history/views/calculator-modern_20251025213357.ejs @@ -0,0 +1,526 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026093437.ejs b/.history/views/calculator-modern_20251026093437.ejs new file mode 100644 index 0000000..9bf386f --- /dev/null +++ b/.history/views/calculator-modern_20251026093437.ejs @@ -0,0 +1,533 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026093540.ejs b/.history/views/calculator-modern_20251026093540.ejs new file mode 100644 index 0000000..c9d7c9b --- /dev/null +++ b/.history/views/calculator-modern_20251026093540.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026093547.ejs b/.history/views/calculator-modern_20251026093547.ejs new file mode 100644 index 0000000..c9d7c9b --- /dev/null +++ b/.history/views/calculator-modern_20251026093547.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094035.ejs b/.history/views/calculator-modern_20251026094035.ejs new file mode 100644 index 0000000..fb77508 --- /dev/null +++ b/.history/views/calculator-modern_20251026094035.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094112.ejs b/.history/views/calculator-modern_20251026094112.ejs new file mode 100644 index 0000000..fb77508 --- /dev/null +++ b/.history/views/calculator-modern_20251026094112.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094635.ejs b/.history/views/calculator-modern_20251026094635.ejs new file mode 100644 index 0000000..fe6808a --- /dev/null +++ b/.history/views/calculator-modern_20251026094635.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094646.ejs b/.history/views/calculator-modern_20251026094646.ejs new file mode 100644 index 0000000..069a58f --- /dev/null +++ b/.history/views/calculator-modern_20251026094646.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094724.ejs b/.history/views/calculator-modern_20251026094724.ejs new file mode 100644 index 0000000..069a58f --- /dev/null +++ b/.history/views/calculator-modern_20251026094724.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094931.ejs b/.history/views/calculator-modern_20251026094931.ejs new file mode 100644 index 0000000..3b05af8 --- /dev/null +++ b/.history/views/calculator-modern_20251026094931.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094938.ejs b/.history/views/calculator-modern_20251026094938.ejs new file mode 100644 index 0000000..f228ce4 --- /dev/null +++ b/.history/views/calculator-modern_20251026094938.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094945.ejs b/.history/views/calculator-modern_20251026094945.ejs new file mode 100644 index 0000000..af76c86 --- /dev/null +++ b/.history/views/calculator-modern_20251026094945.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026094955.ejs b/.history/views/calculator-modern_20251026094955.ejs new file mode 100644 index 0000000..e0ef15b --- /dev/null +++ b/.history/views/calculator-modern_20251026094955.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095003.ejs b/.history/views/calculator-modern_20251026095003.ejs new file mode 100644 index 0000000..c99114e --- /dev/null +++ b/.history/views/calculator-modern_20251026095003.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095026.ejs b/.history/views/calculator-modern_20251026095026.ejs new file mode 100644 index 0000000..c99114e --- /dev/null +++ b/.history/views/calculator-modern_20251026095026.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095207.ejs b/.history/views/calculator-modern_20251026095207.ejs new file mode 100644 index 0000000..ebe6575 --- /dev/null +++ b/.history/views/calculator-modern_20251026095207.ejs @@ -0,0 +1,532 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+

<%- __('calculator.result.estimated_price') %>

+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095223.ejs b/.history/views/calculator-modern_20251026095223.ejs new file mode 100644 index 0000000..2c6937e --- /dev/null +++ b/.history/views/calculator-modern_20251026095223.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095324.ejs b/.history/views/calculator-modern_20251026095324.ejs new file mode 100644 index 0000000..2c6937e --- /dev/null +++ b/.history/views/calculator-modern_20251026095324.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095431.ejs b/.history/views/calculator-modern_20251026095431.ejs new file mode 100644 index 0000000..161af89 --- /dev/null +++ b/.history/views/calculator-modern_20251026095431.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095541.ejs b/.history/views/calculator-modern_20251026095541.ejs new file mode 100644 index 0000000..1240b2d --- /dev/null +++ b/.history/views/calculator-modern_20251026095541.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095552.ejs b/.history/views/calculator-modern_20251026095552.ejs new file mode 100644 index 0000000..c647c78 --- /dev/null +++ b/.history/views/calculator-modern_20251026095552.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095647.ejs b/.history/views/calculator-modern_20251026095647.ejs new file mode 100644 index 0000000..c647c78 --- /dev/null +++ b/.history/views/calculator-modern_20251026095647.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026095942.ejs b/.history/views/calculator-modern_20251026095942.ejs new file mode 100644 index 0000000..cb4ac6a --- /dev/null +++ b/.history/views/calculator-modern_20251026095942.ejs @@ -0,0 +1,523 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100001.ejs b/.history/views/calculator-modern_20251026100001.ejs new file mode 100644 index 0000000..d8247e1 --- /dev/null +++ b/.history/views/calculator-modern_20251026100001.ejs @@ -0,0 +1,575 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+ +
+
+
+
+ Расчет стоимости +
+
₩0
+
+
+ + +
+
+ + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100048.ejs b/.history/views/calculator-modern_20251026100048.ejs new file mode 100644 index 0000000..49760a4 --- /dev/null +++ b/.history/views/calculator-modern_20251026100048.ejs @@ -0,0 +1,578 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+ +
+
+
+
+ Расчет стоимости +
+
₩0
+
+
+ + +
+
+ + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100440.ejs b/.history/views/calculator-modern_20251026100440.ejs new file mode 100644 index 0000000..49760a4 --- /dev/null +++ b/.history/views/calculator-modern_20251026100440.ejs @@ -0,0 +1,578 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+ +
+
+
+
+ Расчет стоимости +
+
₩0
+
+
+ + +
+
+ + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100624.ejs b/.history/views/calculator-modern_20251026100624.ejs new file mode 100644 index 0000000..084fa52 --- /dev/null +++ b/.history/views/calculator-modern_20251026100624.ejs @@ -0,0 +1,541 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100633.ejs b/.history/views/calculator-modern_20251026100633.ejs new file mode 100644 index 0000000..bdcdcd1 --- /dev/null +++ b/.history/views/calculator-modern_20251026100633.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100722.ejs b/.history/views/calculator-modern_20251026100722.ejs new file mode 100644 index 0000000..bdcdcd1 --- /dev/null +++ b/.history/views/calculator-modern_20251026100722.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100902.ejs b/.history/views/calculator-modern_20251026100902.ejs new file mode 100644 index 0000000..a3adef4 --- /dev/null +++ b/.history/views/calculator-modern_20251026100902.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100910.ejs b/.history/views/calculator-modern_20251026100910.ejs new file mode 100644 index 0000000..f676bf3 --- /dev/null +++ b/.history/views/calculator-modern_20251026100910.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100925.ejs b/.history/views/calculator-modern_20251026100925.ejs new file mode 100644 index 0000000..f676bf3 --- /dev/null +++ b/.history/views/calculator-modern_20251026100925.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + +
+
+
+
+
+

<%- __('calculator.result.estimated_price') %>

+
+

+ ₩0 +

+
+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026100938.ejs b/.history/views/calculator-modern_20251026100938.ejs new file mode 100644 index 0000000..ec4f260 --- /dev/null +++ b/.history/views/calculator-modern_20251026100938.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101015.ejs b/.history/views/calculator-modern_20251026101015.ejs new file mode 100644 index 0000000..ec4f260 --- /dev/null +++ b/.history/views/calculator-modern_20251026101015.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101439.ejs b/.history/views/calculator-modern_20251026101439.ejs new file mode 100644 index 0000000..a93e754 --- /dev/null +++ b/.history/views/calculator-modern_20251026101439.ejs @@ -0,0 +1,538 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101457.ejs b/.history/views/calculator-modern_20251026101457.ejs new file mode 100644 index 0000000..f65ad78 --- /dev/null +++ b/.history/views/calculator-modern_20251026101457.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101507.ejs b/.history/views/calculator-modern_20251026101507.ejs new file mode 100644 index 0000000..f65ad78 --- /dev/null +++ b/.history/views/calculator-modern_20251026101507.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101531.ejs b/.history/views/calculator-modern_20251026101531.ejs new file mode 100644 index 0000000..2c71c53 --- /dev/null +++ b/.history/views/calculator-modern_20251026101531.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101541.ejs b/.history/views/calculator-modern_20251026101541.ejs new file mode 100644 index 0000000..2c71c53 --- /dev/null +++ b/.history/views/calculator-modern_20251026101541.ejs @@ -0,0 +1,536 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101615.ejs b/.history/views/calculator-modern_20251026101615.ejs new file mode 100644 index 0000000..af847d5 --- /dev/null +++ b/.history/views/calculator-modern_20251026101615.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026101730.ejs b/.history/views/calculator-modern_20251026101730.ejs new file mode 100644 index 0000000..af847d5 --- /dev/null +++ b/.history/views/calculator-modern_20251026101730.ejs @@ -0,0 +1,535 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102025.ejs b/.history/views/calculator-modern_20251026102025.ejs new file mode 100644 index 0000000..63d5c57 --- /dev/null +++ b/.history/views/calculator-modern_20251026102025.ejs @@ -0,0 +1,537 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102208.ejs b/.history/views/calculator-modern_20251026102208.ejs new file mode 100644 index 0000000..a8a59e9 --- /dev/null +++ b/.history/views/calculator-modern_20251026102208.ejs @@ -0,0 +1,529 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102246.ejs b/.history/views/calculator-modern_20251026102246.ejs new file mode 100644 index 0000000..a8a59e9 --- /dev/null +++ b/.history/views/calculator-modern_20251026102246.ejs @@ -0,0 +1,529 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102616.ejs b/.history/views/calculator-modern_20251026102616.ejs new file mode 100644 index 0000000..e59726e --- /dev/null +++ b/.history/views/calculator-modern_20251026102616.ejs @@ -0,0 +1,530 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102634.ejs b/.history/views/calculator-modern_20251026102634.ejs new file mode 100644 index 0000000..32e1a13 --- /dev/null +++ b/.history/views/calculator-modern_20251026102634.ejs @@ -0,0 +1,530 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102723.ejs b/.history/views/calculator-modern_20251026102723.ejs new file mode 100644 index 0000000..32e1a13 --- /dev/null +++ b/.history/views/calculator-modern_20251026102723.ejs @@ -0,0 +1,530 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+
+ + +
+ + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102728.ejs b/.history/views/calculator-modern_20251026102728.ejs new file mode 100644 index 0000000..2cc5390 --- /dev/null +++ b/.history/views/calculator-modern_20251026102728.ejs @@ -0,0 +1,533 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026102817.ejs b/.history/views/calculator-modern_20251026102817.ejs new file mode 100644 index 0000000..2cc5390 --- /dev/null +++ b/.history/views/calculator-modern_20251026102817.ejs @@ -0,0 +1,533 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026103702.ejs b/.history/views/calculator-modern_20251026103702.ejs new file mode 100644 index 0000000..aaba542 --- /dev/null +++ b/.history/views/calculator-modern_20251026103702.ejs @@ -0,0 +1,572 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-modern_20251026104239.ejs b/.history/views/calculator-modern_20251026104239.ejs new file mode 100644 index 0000000..aaba542 --- /dev/null +++ b/.history/views/calculator-modern_20251026104239.ejs @@ -0,0 +1,572 @@ + + + + + + <%- __('calculator.title') %> | <%= siteSettings?.siteName || 'SmartSolTech' %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+
+ +
+
+

+ <%- __('calculator.title') %> +

+

+ <%- __('calculator.subtitle') %> +

+
+ +
+ + + + + +
+
+ + +
+
+ +
+
+
+ + 1 +
+
+ <%- __('calculator.step1.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 2 +
+
+ <%- __('calculator.step2.nav_title') %> +
+
+
+ + +
+
+
+ + +
+
+
+ + 3 +
+
+ <%- __('calculator.result.nav_title') %> +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + + +
+

<%- __('calculator.step1.title') %>

+

<%- __('calculator.step1.subtitle') %>

+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
+ + <%- __('navigation.services') %> + + ₩500,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
+ + <%- __('navigation.services') %> + + ₩800,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
+ + <%- __('navigation.services') %> + + ₩300,000 +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
+ + <%- __('navigation.services') %> + + ₩200,000 +
+
+
+
+
+ + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/.history/views/calculator-new_20251019181325.ejs b/.history/views/calculator-new_20251019181325.ejs deleted file mode 100644 index e7cb97b..0000000 --- a/.history/views/calculator-new_20251019181325.ejs +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
-
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/calculator-new_20251019181629.ejs b/.history/views/calculator-new_20251019181629.ejs deleted file mode 100644 index e7cb97b..0000000 --- a/.history/views/calculator-new_20251019181629.ejs +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
-
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251019161408.ejs b/.history/views/calculator_20251019161408.ejs deleted file mode 100644 index 69ee69f..0000000 --- a/.history/views/calculator_20251019161408.ejs +++ /dev/null @@ -1,677 +0,0 @@ - -
-
-

- 프로젝트 견적 계산기 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 정확한 견적을 계산해드립니다 -

-
-
- - -
-
-
-
-
- -
-
-

1단계: 서비스 선택

-

필요한 서비스를 선택해주세요 (복수 선택 가능)

-
- -
- -
- -
- -
-
- - -
-
-

2단계: 프로젝트 세부사항

-

프로젝트의 복잡도와 일정을 선택해주세요

-
- -
- -
- -
-
-
-
🌟
-
간단함
-
기본 기능, 표준 디자인
-
-30% 할인
-
-
-
-
-
-
보통
-
중간 복잡도, 커스텀 기능
-
표준 가격
-
-
-
-
-
🚀
-
복잡함
-
고급 기능, 통합 시스템
-
+50% 추가
-
-
-
-
-
💎
-
엔터프라이즈
-
대규모, 높은 복잡도
-
+100% 추가
-
-
-
-
- - -
- -
-
-
-
-
긴급 (2주 이내)
-
+80% 추가
-
-
-
-
-
🔥
-
빠름 (2-4주)
-
+40% 추가
-
-
-
-
-
-
표준 (1-3개월)
-
표준 가격
-
-
-
-
-
🌱
-
여유 (3개월+)
-
-20% 할인
-
-
-
-
-
- -
- - -
-
- - -
-
-

3단계: 추가 기능

-

필요한 추가 기능을 선택해주세요 (선택사항)

-
- -
-
-
-
- SEO 최적화 - ₩300,000 -
-

검색엔진 최적화 및 메타 태그 설정

-
-
- -
-
-
- 분석 도구 설정 - ₩150,000 -
-

Google Analytics, 태그 매니저 설정

-
-
- -
-
-
- 소셜 미디어 연동 - ₩200,000 -
-

Facebook, Instagram, 카카오톡 연동

-
-
- -
-
-
- 결제 시스템 - ₩500,000 -
-

신용카드, 간편결제 시스템 연동

-
-
- -
-
-
- 다국어 지원 - ₩400,000 -
-

한국어, 영어, 중국어 등 다국어

-
-
- -
-
-
- 관리자 패널 - ₩600,000 -
-

콘텐츠 관리, 사용자 관리 시스템

-
-
-
- - -
- - -
- -
- - -
-
- - -
-
-

견적 결과

-

계산된 프로젝트 견적을 확인하세요

-
- -
- -
- - -
-

견적 요청하기

-

정확한 견적을 받으시려면 연락처를 남겨주세요

- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -
- -
- -
-
- -
- -
-
-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251019162545.ejs b/.history/views/calculator_20251019162545.ejs deleted file mode 100644 index 69ee69f..0000000 --- a/.history/views/calculator_20251019162545.ejs +++ /dev/null @@ -1,677 +0,0 @@ - -
-
-

- 프로젝트 견적 계산기 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 정확한 견적을 계산해드립니다 -

-
-
- - -
-
-
-
-
- -
-
-

1단계: 서비스 선택

-

필요한 서비스를 선택해주세요 (복수 선택 가능)

-
- -
- -
- -
- -
-
- - -
-
-

2단계: 프로젝트 세부사항

-

프로젝트의 복잡도와 일정을 선택해주세요

-
- -
- -
- -
-
-
-
🌟
-
간단함
-
기본 기능, 표준 디자인
-
-30% 할인
-
-
-
-
-
-
보통
-
중간 복잡도, 커스텀 기능
-
표준 가격
-
-
-
-
-
🚀
-
복잡함
-
고급 기능, 통합 시스템
-
+50% 추가
-
-
-
-
-
💎
-
엔터프라이즈
-
대규모, 높은 복잡도
-
+100% 추가
-
-
-
-
- - -
- -
-
-
-
-
긴급 (2주 이내)
-
+80% 추가
-
-
-
-
-
🔥
-
빠름 (2-4주)
-
+40% 추가
-
-
-
-
-
-
표준 (1-3개월)
-
표준 가격
-
-
-
-
-
🌱
-
여유 (3개월+)
-
-20% 할인
-
-
-
-
-
- -
- - -
-
- - -
-
-

3단계: 추가 기능

-

필요한 추가 기능을 선택해주세요 (선택사항)

-
- -
-
-
-
- SEO 최적화 - ₩300,000 -
-

검색엔진 최적화 및 메타 태그 설정

-
-
- -
-
-
- 분석 도구 설정 - ₩150,000 -
-

Google Analytics, 태그 매니저 설정

-
-
- -
-
-
- 소셜 미디어 연동 - ₩200,000 -
-

Facebook, Instagram, 카카오톡 연동

-
-
- -
-
-
- 결제 시스템 - ₩500,000 -
-

신용카드, 간편결제 시스템 연동

-
-
- -
-
-
- 다국어 지원 - ₩400,000 -
-

한국어, 영어, 중국어 등 다국어

-
-
- -
-
-
- 관리자 패널 - ₩600,000 -
-

콘텐츠 관리, 사용자 관리 시스템

-
-
-
- - -
- - -
- -
- - -
-
- - -
-
-

견적 결과

-

계산된 프로젝트 견적을 확인하세요

-
- -
- -
- - -
-

견적 요청하기

-

정확한 견적을 받으시려면 연락처를 남겨주세요

- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -
- -
- -
-
- -
- -
-
-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251019182247.ejs b/.history/views/calculator_20251019182247.ejs deleted file mode 100644 index c93022f..0000000 --- a/.history/views/calculator_20251019182247.ejs +++ /dev/null @@ -1,494 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
- -
-
-
- -
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251019182320.ejs b/.history/views/calculator_20251019182320.ejs deleted file mode 100644 index f876cf5..0000000 --- a/.history/views/calculator_20251019182320.ejs +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
- -
-
-
- -
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251019182628.ejs b/.history/views/calculator_20251019182628.ejs deleted file mode 100644 index f876cf5..0000000 --- a/.history/views/calculator_20251019182628.ejs +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
- -
-
-
- -
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251021212440.ejs b/.history/views/calculator_20251021212440.ejs deleted file mode 100644 index 1b57f80..0000000 --- a/.history/views/calculator_20251021212440.ejs +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
- -
-
-
- -
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/calculator_20251021212532.ejs b/.history/views/calculator_20251021212532.ejs deleted file mode 100644 index 1b57f80..0000000 --- a/.history/views/calculator_20251021212532.ejs +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - <%- __('calculator.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('calculator.title') %> -

-

- <%- __('calculator.subtitle') %> -

-
-
- - -
-
-
-
- -
-
-
- -
- -
-
-

- <%- __('calculator.step1.title') %> -

-

- <%- __('calculator.step1.subtitle') %> -

-
- -
- -
-
-
- -
-
-

- <%- __('services.web.title') %> -

-
<%- __('services.web.price') %>
-
-
-

- <%- __('services.web.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.mobile.title') %> -

-
<%- __('services.mobile.price') %>
-
-
-

- <%- __('services.mobile.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.design.title') %> -

-
<%- __('services.design.price') %>
-
-
-

- <%- __('services.design.description') %> -

-
- - -
-
-
- -
-
-

- <%- __('services.marketing.title') %> -

-
<%- __('services.marketing.price') %>
-
-
-

- <%- __('services.marketing.description') %> -

-
-
- -
- -
-
- - - - - - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/contact-new_20251019174651.ejs b/.history/views/contact-new_20251019174651.ejs deleted file mode 100644 index b7efc4e..0000000 --- a/.history/views/contact-new_20251019174651.ejs +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - <%- __('contact.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('contact.hero.title') %> -

-

- <%- __('contact.hero.subtitle') %> -

-
-
- - -
-
-
- -
-

- <%- __('contact.form.title') %> -

-
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- - -
- - -
-
- - -
-

- <%- __('contact.info.title') %> -

- -
- -
-
- -
-
-

- <%- __('contact.phone.title') %> -

-

- <%- __('contact.phone.number') %> -

-

- <%- __('contact.phone.hours') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.email.title') %> -

-

- <%- __('contact.email.address') %> -

-

- <%- __('contact.email.response') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.telegram.title') %> -

-

- @smartsoltech -

-

- <%- __('contact.telegram.subtitle') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.address.title') %> -

-

- <%- __('contact.address.line1') %>
- <%- __('contact.address.line2') %> -

-
-
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/contact-new_20251019174704.ejs b/.history/views/contact-new_20251019174704.ejs deleted file mode 100644 index b7efc4e..0000000 --- a/.history/views/contact-new_20251019174704.ejs +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - <%- __('contact.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('contact.hero.title') %> -

-

- <%- __('contact.hero.subtitle') %> -

-
-
- - -
-
-
- -
-

- <%- __('contact.form.title') %> -

-
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- - -
- - -
-
- - -
-

- <%- __('contact.info.title') %> -

- -
- -
-
- -
-
-

- <%- __('contact.phone.title') %> -

-

- <%- __('contact.phone.number') %> -

-

- <%- __('contact.phone.hours') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.email.title') %> -

-

- <%- __('contact.email.address') %> -

-

- <%- __('contact.email.response') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.telegram.title') %> -

-

- @smartsoltech -

-

- <%- __('contact.telegram.subtitle') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.address.title') %> -

-

- <%- __('contact.address.line1') %>
- <%- __('contact.address.line2') %> -

-
-
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251019162940.ejs b/.history/views/contact_20251019162940.ejs deleted file mode 100644 index ca869f8..0000000 --- a/.history/views/contact_20251019162940.ejs +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - 연락처 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 연락처 -

-

- 프로젝트 문의부터 기술 상담까지, 언제든 연락주세요 -

-
-
- - -
-
-
- - -
-
-

- 프로젝트 문의하기 -

-

- 아래 양식을 작성해 주시면 24시간 내에 답변드리겠습니다. -

-
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - - - - -
- - -
- - -
-

- 연락처 정보 -

- -
- -
-
- -
- -
- - -
-
- -
-
-

전화번호

- - +82-10-1234-5678 - -
-
- - -
-
- -
-
-

주소

-

Seoul, South Korea

-
-
- - -
-
- -
-
-

운영시간

-

평일 09:00 - 18:00

-

주말 및 공휴일 휴무

-
-
-
-
- - -
-

- 소셜 미디어 -

- - -
- - - -
-
-
-
- - -
-
-
-

- 자주 묻는 질문 -

-

- 프로젝트와 관련된 궁금한 점들을 확인해보세요 -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251019163807.ejs b/.history/views/contact_20251019163807.ejs deleted file mode 100644 index ca869f8..0000000 --- a/.history/views/contact_20251019163807.ejs +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - 연락처 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 연락처 -

-

- 프로젝트 문의부터 기술 상담까지, 언제든 연락주세요 -

-
-
- - -
-
-
- - -
-
-

- 프로젝트 문의하기 -

-

- 아래 양식을 작성해 주시면 24시간 내에 답변드리겠습니다. -

-
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - - - - -
- - -
- - -
-

- 연락처 정보 -

- -
- -
-
- -
- -
- - -
-
- -
-
-

전화번호

- - +82-10-1234-5678 - -
-
- - -
-
- -
-
-

주소

-

Seoul, South Korea

-
-
- - -
-
- -
-
-

운영시간

-

평일 09:00 - 18:00

-

주말 및 공휴일 휴무

-
-
-
-
- - -
-

- 소셜 미디어 -

- - -
- - - -
-
-
-
- - -
-
-
-

- 자주 묻는 질문 -

-

- 프로젝트와 관련된 궁금한 점들을 확인해보세요 -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251019164437.ejs b/.history/views/contact_20251019164437.ejs deleted file mode 100644 index e3c18f4..0000000 --- a/.history/views/contact_20251019164437.ejs +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - 연락처 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 연락처 -

-

- 프로젝트 문의부터 기술 상담까지, 언제든 연락주세요 -

-
-
- - -
-
-
- - -
-
-

- 프로젝트 문의하기 -

-

- 아래 양식을 작성해 주시면 24시간 내에 답변드리겠습니다. -

-
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - - - - -
- - -
- - -
-

- 연락처 정보 -

- -
- -
-
- -
- -
- - -
-
- -
-
-

전화번호

- - +82-10-1234-5678 - -
-
- - -
-
- -
-
-

주소

-

Seoul, South Korea

-
-
- - -
-
- -
-
-

운영시간

-

평일 09:00 - 18:00

-

주말 및 공휴일 휴무

-
-
-
-
- - -
-

- 소셜 미디어 -

- - -
- - - -
-
-
-
- - -
-
-
-

- 자주 묻는 질문 -

-

- 프로젝트와 관련된 궁금한 점들을 확인해보세요 -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251019165556.ejs b/.history/views/contact_20251019165556.ejs deleted file mode 100644 index e3c18f4..0000000 --- a/.history/views/contact_20251019165556.ejs +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - 연락처 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 연락처 -

-

- 프로젝트 문의부터 기술 상담까지, 언제든 연락주세요 -

-
-
- - -
-
-
- - -
-
-

- 프로젝트 문의하기 -

-

- 아래 양식을 작성해 주시면 24시간 내에 답변드리겠습니다. -

-
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - - - - -
- - -
- - -
-

- 연락처 정보 -

- -
- -
-
- -
- -
- - -
-
- -
-
-

전화번호

- - +82-10-1234-5678 - -
-
- - -
-
- -
-
-

주소

-

Seoul, South Korea

-
-
- - -
-
- -
-
-

운영시간

-

평일 09:00 - 18:00

-

주말 및 공휴일 휴무

-
-
-
-
- - -
-

- 소셜 미디어 -

- - -
- - - -
-
-
-
- - -
-
-
-

- 자주 묻는 질문 -

-

- 프로젝트와 관련된 궁금한 점들을 확인해보세요 -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251019171039.ejs b/.history/views/contact_20251019171039.ejs deleted file mode 100644 index 2a11d23..0000000 --- a/.history/views/contact_20251019171039.ejs +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - 연락처 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 연락처 -

-

- 프로젝트 문의부터 기술 상담까지, 언제든 연락주세요 -

-
-
- - -
-
-
- - -
-
-

- 프로젝트 문의하기 -

-

- 아래 양식을 작성해 주시면 24시간 내에 답변드리겠습니다. -

-
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - - - - -
- - -
- - -
-

- 연락처 정보 -

- -
- -
-
- -
- -
- - -
-
- -
-
-

전화번호

- - +82-10-1234-5678 - -
-
- - -
-
- -
-
-

주소

-

Seoul, South Korea

-
-
- - -
-
- -
-
-

운영시간

-

평일 09:00 - 18:00

-

주말 및 공휴일 휴무

-
-
-
-
- - -
-

- 소셜 미디어 -

- - -
- - - -
-
-
-
- - -
-
-
-

- 자주 묻는 질문 -

-

- 프로젝트와 관련된 궁금한 점들을 확인해보세요 -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251019171203.ejs b/.history/views/contact_20251019171203.ejs deleted file mode 100644 index 2a11d23..0000000 --- a/.history/views/contact_20251019171203.ejs +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - 연락처 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 연락처 -

-

- 프로젝트 문의부터 기술 상담까지, 언제든 연락주세요 -

-
-
- - -
-
-
- - -
-
-

- 프로젝트 문의하기 -

-

- 아래 양식을 작성해 주시면 24시간 내에 답변드리겠습니다. -

-
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - - - - -
- - -
- - -
-

- 연락처 정보 -

- -
- -
-
- -
- -
- - -
-
- -
-
-

전화번호

- - +82-10-1234-5678 - -
-
- - -
-
- -
-
-

주소

-

Seoul, South Korea

-
-
- - -
-
- -
-
-

운영시간

-

평일 09:00 - 18:00

-

주말 및 공휴일 휴무

-
-
-
-
- - -
-

- 소셜 미디어 -

- - -
- - - -
-
-
-
- - -
-
-
-

- 자주 묻는 질문 -

-

- 프로젝트와 관련된 궁금한 점들을 확인해보세요 -

-
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251021212428.ejs b/.history/views/contact_20251021212428.ejs deleted file mode 100644 index cd4dbb3..0000000 --- a/.history/views/contact_20251021212428.ejs +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - <%- __('contact.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('contact.hero.title') %> -

-

- <%- __('contact.hero.subtitle') %> -

-
-
- - -
-
-
- -
-

- <%- __('contact.form.title') %> -

-
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- - -
- - -
-
- - -
-

- <%- __('contact.info.title') %> -

- -
- -
-
- -
-
-

- <%- __('contact.phone.title') %> -

-

- <%- __('contact.phone.number') %> -

-

- <%- __('contact.phone.hours') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.email.title') %> -

-

- <%- __('contact.email.address') %> -

-

- <%- __('contact.email.response') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.telegram.title') %> -

-

- @smartsoltech -

-

- <%- __('contact.telegram.subtitle') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.address.title') %> -

-

- <%- __('contact.address.line1') %>
- <%- __('contact.address.line2') %> -

-
-
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/contact_20251021212532.ejs b/.history/views/contact_20251021212532.ejs deleted file mode 100644 index cd4dbb3..0000000 --- a/.history/views/contact_20251021212532.ejs +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - <%- __('contact.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- <%- __('contact.hero.title') %> -

-

- <%- __('contact.hero.subtitle') %> -

-
-
- - -
-
-
- -
-

- <%- __('contact.form.title') %> -

-
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- - -
- - -
-
- - -
-

- <%- __('contact.info.title') %> -

- -
- -
-
- -
-
-

- <%- __('contact.phone.title') %> -

-

- <%- __('contact.phone.number') %> -

-

- <%- __('contact.phone.hours') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.email.title') %> -

-

- <%- __('contact.email.address') %> -

-

- <%- __('contact.email.response') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.telegram.title') %> -

-

- @smartsoltech -

-

- <%- __('contact.telegram.subtitle') %> -

-
-
- - -
-
- -
-
-

- <%- __('contact.address.title') %> -

-

- <%- __('contact.address.line1') %>
- <%- __('contact.address.line2') %> -

-
-
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - \ No newline at end of file diff --git a/.history/views/error_20251019163708.ejs b/.history/views/error_20251019163708.ejs deleted file mode 100644 index 39b8900..0000000 --- a/.history/views/error_20251019163708.ejs +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - 오류 - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

- -
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251019163807.ejs b/.history/views/error_20251019163807.ejs deleted file mode 100644 index 39b8900..0000000 --- a/.history/views/error_20251019163807.ejs +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - 오류 - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

- -
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251020035536.ejs b/.history/views/error_20251020035536.ejs deleted file mode 100644 index 6c427dc..0000000 --- a/.history/views/error_20251020035536.ejs +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - <%= title || '오류 - SmartSolTech' %> - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251020035856.ejs b/.history/views/error_20251020035856.ejs deleted file mode 100644 index 6c427dc..0000000 --- a/.history/views/error_20251020035856.ejs +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - <%= title || '오류 - SmartSolTech' %> - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251020041605.ejs b/.history/views/error_20251020041605.ejs deleted file mode 100644 index e5e44f2..0000000 --- a/.history/views/error_20251020041605.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || '오류 - SmartSolTech' %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251020041613.ejs b/.history/views/error_20251020041613.ejs deleted file mode 100644 index 17e1d74..0000000 --- a/.history/views/error_20251020041613.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || '오류 - SmartSolTech' %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251020041618.ejs b/.history/views/error_20251020041618.ejs deleted file mode 100644 index f7da80d..0000000 --- a/.history/views/error_20251020041618.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || '오류 - SmartSolTech' %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251020041711.ejs b/.history/views/error_20251020041711.ejs deleted file mode 100644 index f7da80d..0000000 --- a/.history/views/error_20251020041711.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || '오류 - SmartSolTech' %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251021184031.ejs b/.history/views/error_20251021184031.ejs deleted file mode 100644 index 23094bd..0000000 --- a/.history/views/error_20251021184031.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || __('errors.title') %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || '오류가 발생했습니다' %> -

- - -

- <%= message || '요청을 처리하는 중 문제가 발생했습니다.' %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251021184045.ejs b/.history/views/error_20251021184045.ejs deleted file mode 100644 index 63c7d3f..0000000 --- a/.history/views/error_20251021184045.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || __('errors.title') %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || __('errors.default_title') %> -

- - -

- <%= message || __('errors.default_message') %> -

- - -
- - - 홈으로 돌아가기 - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251021184055.ejs b/.history/views/error_20251021184055.ejs deleted file mode 100644 index 97b9db4..0000000 --- a/.history/views/error_20251021184055.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || __('errors.title') %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || __('errors.default_title') %> -

- - -

- <%= message || __('errors.default_message') %> -

- - -
- - - <%= __('errors.back_home') %> - - -
- - -
-

도움이 필요하신가요?

-

- 문제가 지속되면 언제든지 저희에게 연락해 주세요. -

-
- - - 문의하기 - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251021184106.ejs b/.history/views/error_20251021184106.ejs deleted file mode 100644 index 772066c..0000000 --- a/.history/views/error_20251021184106.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || __('errors.title') %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || __('errors.default_title') %> -

- - -

- <%= message || __('errors.default_message') %> -

- - -
- - - <%= __('errors.back_home') %> - - -
- - -
-

<%= __('errors.need_help') %>

-

- <%= __('errors.help_message') %> -

-
- - - <%= __('errors.contact_support') %> - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/error_20251021184308.ejs b/.history/views/error_20251021184308.ejs deleted file mode 100644 index 772066c..0000000 --- a/.history/views/error_20251021184308.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - <%= title || __('errors.title') %> - - - - - - - - - - - - - - - <%- include('partials/navigation', { settings: settings || {}, currentPage: 'error' }) %> - - -
-
-
- - -
- -
- - -

- <%= title || __('errors.default_title') %> -

- - -

- <%= message || __('errors.default_message') %> -

- - -
- - - <%= __('errors.back_home') %> - - -
- - -
-

<%= __('errors.need_help') %>

-

- <%= __('errors.help_message') %> -

-
- - - <%= __('errors.contact_support') %> - - <% if (settings && settings.contact && settings.contact.email) { %> - - - <%= settings.contact.email %> - - <% } else { %> - - - info@smartsoltech.kr - - <% } %> - <% if (settings && settings.contact && settings.contact.phone) { %> - - - <%= settings.contact.phone %> - - <% } else { %> - - - +82-10-1234-5678 - - <% } %> -
-
-
-
-
- - - - - \ No newline at end of file diff --git a/.history/views/index-new_20251019170311.ejs b/.history/views/index-new_20251019170311.ejs deleted file mode 100644 index 8559f2e..0000000 --- a/.history/views/index-new_20251019170311.ejs +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - <%= title || 'SmartSolTech - 혁신적인 기술 솔루션' %> - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
-
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- E-commerce - - 1,234 - -
-

온라인 쇼핑몰 플랫폼

-

현대적인 UI/UX와 완벽한 결제 시스템을 갖춘 전자상거래 솔루션

- - 자세히 보기 - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
+82-10-0000-0000
-
-
-
-
- -
-
-
이메일 문의
-
info@smartsoltech.kr
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index-new_20251019170533.ejs b/.history/views/index-new_20251019170533.ejs deleted file mode 100644 index 8559f2e..0000000 --- a/.history/views/index-new_20251019170533.ejs +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - <%= title || 'SmartSolTech - 혁신적인 기술 솔루션' %> - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
-
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- E-commerce - - 1,234 - -
-

온라인 쇼핑몰 플랫폼

-

현대적인 UI/UX와 완벽한 결제 시스템을 갖춘 전자상거래 솔루션

- - 자세히 보기 - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
+82-10-0000-0000
-
-
-
-
- -
-
-
이메일 문의
-
info@smartsoltech.kr
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index-new_20251019174028.ejs b/.history/views/index-new_20251019174028.ejs deleted file mode 100644 index 2a60a82..0000000 --- a/.history/views/index-new_20251019174028.ejs +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - <%- __(title || 'meta.title') %> - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index-new_20251019174051.ejs b/.history/views/index-new_20251019174051.ejs deleted file mode 100644 index 2a60a82..0000000 --- a/.history/views/index-new_20251019174051.ejs +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - <%- __(title || 'meta.title') %> - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251019161225.ejs b/.history/views/index_20251019161225.ejs deleted file mode 100644 index e1d69e2..0000000 --- a/.history/views/index_20251019161225.ejs +++ /dev/null @@ -1,310 +0,0 @@ - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- <% if (featuredServices && featuredServices.length > 0) { %> - <% featuredServices.forEach((service, index) => { %> -
-
- -
-

<%= service.name %>

-

<%= service.shortDescription %>

-
- ₩<%= service.pricing.basePrice.toLocaleString() %>~ -
-
- <% }) %> - <% } else { %> - -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
- <% } %> -
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.primaryImage) { %> - <%= project.primaryImage.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount %> - -
-

<%= project.title %>

-

<%= project.shortDescription %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
<%= settings && settings.contact ? settings.contact.phone : '+82-10-0000-0000' %>
-
-
-
-
- -
-
-
이메일 문의
-
<%= settings && settings.contact ? settings.contact.email : 'info@smartsoltech.kr' %>
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/.history/views/index_20251019162544.ejs b/.history/views/index_20251019162544.ejs deleted file mode 100644 index e1d69e2..0000000 --- a/.history/views/index_20251019162544.ejs +++ /dev/null @@ -1,310 +0,0 @@ - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- <% if (featuredServices && featuredServices.length > 0) { %> - <% featuredServices.forEach((service, index) => { %> -
-
- -
-

<%= service.name %>

-

<%= service.shortDescription %>

-
- ₩<%= service.pricing.basePrice.toLocaleString() %>~ -
-
- <% }) %> - <% } else { %> - -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
- <% } %> -
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.primaryImage) { %> - <%= project.primaryImage.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount %> - -
-

<%= project.title %>

-

<%= project.shortDescription %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
<%= settings && settings.contact ? settings.contact.phone : '+82-10-0000-0000' %>
-
-
-
-
- -
-
-
이메일 문의
-
<%= settings && settings.contact ? settings.contact.email : 'info@smartsoltech.kr' %>
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/.history/views/index_20251019171013.ejs b/.history/views/index_20251019171013.ejs deleted file mode 100644 index a95ab66..0000000 --- a/.history/views/index_20251019171013.ejs +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - <%= title || 'SmartSolTech - 혁신적인 기술 솔루션' %> - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
-
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- E-commerce - - 1,234 - -
-

온라인 쇼핑몰 플랫폼

-

현대적인 UI/UX와 완벽한 결제 시스템을 갖춘 전자상거래 솔루션

- - 자세히 보기 - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
+82-10-0000-0000
-
-
-
-
- -
-
-
이메일 문의
-
info@smartsoltech.kr
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251019171203.ejs b/.history/views/index_20251019171203.ejs deleted file mode 100644 index a95ab66..0000000 --- a/.history/views/index_20251019171203.ejs +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - <%= title || 'SmartSolTech - 혁신적인 기술 솔루션' %> - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
-
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- E-commerce - - 1,234 - -
-

온라인 쇼핑몰 플랫폼

-

현대적인 UI/UX와 완벽한 결제 시스템을 갖춘 전자상거래 솔루션

- - 자세히 보기 - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
+82-10-0000-0000
-
-
-
-
- -
-
-
이메일 문의
-
info@smartsoltech.kr
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045504.ejs b/.history/views/index_20251020045504.ejs deleted file mode 100644 index 18511de..0000000 --- a/.history/views/index_20251020045504.ejs +++ /dev/null @@ -1,382 +0,0 @@ -<% layout('layout') -%> -<%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045528.ejs b/.history/views/index_20251020045528.ejs deleted file mode 100644 index 18511de..0000000 --- a/.history/views/index_20251020045528.ejs +++ /dev/null @@ -1,382 +0,0 @@ -<% layout('layout') -%> -<%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045552.ejs b/.history/views/index_20251020045552.ejs deleted file mode 100644 index 16f57b7..0000000 --- a/.history/views/index_20251020045552.ejs +++ /dev/null @@ -1,435 +0,0 @@ - - - - - - <%- __(title || 'meta.title') %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045603.ejs b/.history/views/index_20251020045603.ejs deleted file mode 100644 index 848454a..0000000 --- a/.history/views/index_20251020045603.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- __(title || 'meta.title') %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045707.ejs b/.history/views/index_20251020045707.ejs deleted file mode 100644 index 848454a..0000000 --- a/.history/views/index_20251020045707.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- __(title || 'meta.title') %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045942.ejs b/.history/views/index_20251020045942.ejs deleted file mode 100644 index 94a025c..0000000 --- a/.history/views/index_20251020045942.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045946.ejs b/.history/views/index_20251020045946.ejs deleted file mode 100644 index 94a025c..0000000 --- a/.history/views/index_20251020045946.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045954.ejs b/.history/views/index_20251020045954.ejs deleted file mode 100644 index 66d917d..0000000 --- a/.history/views/index_20251020045954.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020050006.ejs b/.history/views/index_20251020050006.ejs deleted file mode 100644 index 66d917d..0000000 --- a/.history/views/index_20251020050006.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251021172735.ejs b/.history/views/index_20251021172735.ejs deleted file mode 100644 index 9d9e6dd..0000000 --- a/.history/views/index_20251021172735.ejs +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251021172757.ejs b/.history/views/index_20251021172757.ejs deleted file mode 100644 index 3fdc222..0000000 --- a/.history/views/index_20251021172757.ejs +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - 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; - } - `; - document.head.appendChild(style); - - // Contact form handler - document.getElementById('quick-contact-form').addEventListener('submit', function(e) { - e.preventDefault(); - const formData = new FormData(this); - - fetch('/contact', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - alert('<%- __("contact.form.success") %>'); - this.reset(); - } else { - alert('<%- __("contact.form.error") %>'); - } - }) - .catch(error => { - console.error('Error:', error); - alert('<%- __("contact.form.error") %>'); - }); - }); - - // Theme initialization - document.addEventListener('DOMContentLoaded', function() { - const theme = localStorage.getItem('theme') || 'light'; - document.documentElement.className = theme === 'dark' ? 'dark' : ''; - }); - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251021172802.ejs b/.history/views/index_20251021172802.ejs deleted file mode 100644 index 3fdc222..0000000 --- a/.history/views/index_20251021172802.ejs +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - 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; - } - `; - document.head.appendChild(style); - - // Contact form handler - document.getElementById('quick-contact-form').addEventListener('submit', function(e) { - e.preventDefault(); - const formData = new FormData(this); - - fetch('/contact', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - alert('<%- __("contact.form.success") %>'); - this.reset(); - } else { - alert('<%- __("contact.form.error") %>'); - } - }) - .catch(error => { - console.error('Error:', error); - alert('<%- __("contact.form.error") %>'); - }); - }); - - // Theme initialization - document.addEventListener('DOMContentLoaded', function() { - const theme = localStorage.getItem('theme') || 'light'; - document.documentElement.className = theme === 'dark' ? 'dark' : ''; - }); - - <%- include('partials/footer') %> - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251021172824.ejs b/.history/views/index_20251021172824.ejs deleted file mode 100644 index f6fcb0c..0000000 --- a/.history/views/index_20251021172824.ejs +++ /dev/null @@ -1,394 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251021172830.ejs b/.history/views/index_20251021172830.ejs deleted file mode 100644 index f6fcb0c..0000000 --- a/.history/views/index_20251021172830.ejs +++ /dev/null @@ -1,394 +0,0 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- <%- __('hero.title.smart') %> - <%- __('hero.title.solutions') %> -

-

- <%- __('hero.subtitle') %> -

- -
- - -
- - - -
-
-
- - -
-
-
-

- <%- __('services.title.our') %> <%- __('services.title.services') %> -

-

- <%- __('services.subtitle') %> -

-
- -
- -
-
- -
-

<%- __('services.web.title') %>

-

<%- __('services.web.description') %>

-
<%- __('services.web.price') %>
-
- - -
-
- -
-

<%- __('services.mobile.title') %>

-

<%- __('services.mobile.description') %>

-
<%- __('services.mobile.price') %>
-
- - -
-
- -
-

<%- __('services.design.title') %>

-

<%- __('services.design.description') %>

-
<%- __('services.design.price') %>
-
- - -
-
- -
-

<%- __('services.marketing.title') %>

-

<%- __('services.marketing.description') %>

-
<%- __('services.marketing.price') %>
-
-
- - -
-
- - -
-
-
-

- <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> -

-

- <%- __('portfolio.subtitle') %> -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - <%- __('common.view_details') %> - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- <%- __('portfolio.default.ecommerce') %> - - 1,234 - -
-

<%- __('portfolio.default.title') %>

-

<%- __('portfolio.default.description') %>

- - <%- __('common.view_details') %> - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- <%- __('calculator.cta.title') %> -

-

- <%- __('calculator.cta.subtitle') %> -

- - - <%- __('calculator.cta.button') %> - -
-
-
- - -
-
-
-
-

- <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> -

-

- <%- __('contact.cta.subtitle') %> -

-
-
-
- -
-
-
<%- __('contact.phone.title') %>
-
<%- __('contact.phone.number') %>
-
-
-
-
- -
-
-
<%- __('contact.email.title') %>
-
<%- __('contact.email.address') %>
-
-
-
-
- -
-
-
<%- __('contact.telegram.title') %>
-
<%- __('contact.telegram.subtitle') %>
-
-
-
-
-
-
-

<%- __('contact.form.title') %>

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - - - - - - - \ No newline at end of file diff --git a/.history/views/index_20251020045441.ejs b/.history/views/index_20251025213456.ejs similarity index 91% rename from .history/views/index_20251020045441.ejs rename to .history/views/index_20251025213456.ejs index 1852428..9b3e140 100644 --- a/.history/views/index_20251020045441.ejs +++ b/.history/views/index_20251025213456.ejs @@ -1,5 +1,4 @@ -<% layout('layout') -%> -<%- include('partials/navigation') %> +
@@ -15,7 +14,7 @@
-

+

<%- __('hero.title.smart') %> <%- __('hero.title.solutions') %>

@@ -303,69 +302,15 @@
- <%- include('partials/footer') %> - - - - - - - \ No newline at end of file + } + }); + \ No newline at end of file diff --git a/.history/views/index_20251020045457.ejs b/.history/views/index_20251025213505.ejs similarity index 91% rename from .history/views/index_20251020045457.ejs rename to .history/views/index_20251025213505.ejs index 1852428..9b3e140 100644 --- a/.history/views/index_20251020045457.ejs +++ b/.history/views/index_20251025213505.ejs @@ -1,5 +1,4 @@ -<% layout('layout') -%> -<%- include('partials/navigation') %> +
@@ -15,7 +14,7 @@
-

+

<%- __('hero.title.smart') %> <%- __('hero.title.solutions') %>

@@ -303,69 +302,15 @@
- <%- include('partials/footer') %> - - - - - - - \ No newline at end of file + } + }); + \ No newline at end of file diff --git a/.history/views/index_20251020045539.ejs b/.history/views/index_20251025213517.ejs similarity index 90% rename from .history/views/index_20251020045539.ejs rename to .history/views/index_20251025213517.ejs index 2e4a8ce..3693a16 100644 --- a/.history/views/index_20251020045539.ejs +++ b/.history/views/index_20251025213517.ejs @@ -1,4 +1,4 @@ -<%- include('partials/navigation') %> +
@@ -14,7 +14,7 @@
-

+

<%- __('hero.title.smart') %> <%- __('hero.title.solutions') %>

@@ -44,10 +44,10 @@
-

+

<%- __('services.title.our') %> <%- __('services.title.services') %>

-

+

<%- __('services.subtitle') %>

@@ -302,73 +302,8 @@
- <%- include('partials/footer') %> - - - - - - + - - - + \ No newline at end of file diff --git a/.history/views/index_20251025213557.ejs b/.history/views/index_20251025213557.ejs new file mode 100644 index 0000000..8db7ce4 --- /dev/null +++ b/.history/views/index_20251025213557.ejs @@ -0,0 +1,316 @@ + + + +
+
+
+ + +
+
+
+
+
+ +
+
+

+ <%- __('hero.title.smart') %> + <%- __('hero.title.solutions') %> +

+

+ <%- __('hero.subtitle') %> +

+ +
+ + +
+ + + +
+
+
+ + +
+
+
+

+ <%- __('services.title.our') %> <%- __('services.title.services') %> +

+

+ <%- __('services.subtitle') %> +

+
+ +
+ +
+
+ +
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
<%- __('services.web.price') %>
+
+ + +
+
+ +
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
<%- __('services.mobile.price') %>
+
+ + +
+
+ +
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
<%- __('services.design.price') %>
+
+ + +
+
+ +
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
<%- __('services.marketing.price') %>
+
+
+ + +
+
+ + +
+
+
+

+ <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> +

+

+ <%- __('portfolio.subtitle') %> +

+
+ +
+ <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> + <% featuredPortfolio.forEach((project, index) => { %> +
+
+ <% if (project.images && project.images.length > 0) { %> + <%= project.images.find(img => img.isPrimary)?.alt || project.title %> + <% } else { %> +
+ <%= project.title.charAt(0) %> +
+ <% } %> +
+
+
+ <% if (project.technologies && project.technologies.length > 0) { %> + <% project.technologies.slice(0, 3).forEach(tech => { %> + <%= tech %> + <% }) %> + <% } %> +
+
+
+
+
+ <%= project.category %> + + <%= project.viewCount || 0 %> + +
+

<%= project.title %>

+

<%= project.shortDescription || project.description %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% }) %> + <% } else { %> + +
+
+
+ E +
+
+
+
+ React + Node.js +
+
+
+
+
+ <%- __('portfolio.default.ecommerce') %> + + 1,234 + +
+

<%- __('portfolio.default.title') %>

+

<%- __('portfolio.default.description') %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% } %> +
+ + +
+
+ + +
+
+
+

+ <%- __('calculator.cta.title') %> +

+

+ <%- __('calculator.cta.subtitle') %> +

+ + + <%- __('calculator.cta.button') %> + +
+
+
+ + +
+
+
+
+

+ <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> +

+

+ <%- __('contact.cta.subtitle') %> +

+
+
+
+ +
+
+
<%- __('contact.phone.title') %>
+
<%- __('contact.phone.number') %>
+
+
+
+
+ +
+
+
<%- __('contact.email.title') %>
+
<%- __('contact.email.address') %>
+
+
+
+
+ +
+
+
<%- __('contact.telegram.title') %>
+
<%- __('contact.telegram.subtitle') %>
+
+
+
+
+
+
+

<%- __('contact.form.title') %>

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/.history/views/index_20251025213641.ejs b/.history/views/index_20251025213641.ejs new file mode 100644 index 0000000..8db7ce4 --- /dev/null +++ b/.history/views/index_20251025213641.ejs @@ -0,0 +1,316 @@ + + + +
+
+
+ + +
+
+
+
+
+ +
+
+

+ <%- __('hero.title.smart') %> + <%- __('hero.title.solutions') %> +

+

+ <%- __('hero.subtitle') %> +

+ +
+ + +
+ + + +
+
+
+ + +
+
+
+

+ <%- __('services.title.our') %> <%- __('services.title.services') %> +

+

+ <%- __('services.subtitle') %> +

+
+ +
+ +
+
+ +
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
<%- __('services.web.price') %>
+
+ + +
+
+ +
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
<%- __('services.mobile.price') %>
+
+ + +
+
+ +
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
<%- __('services.design.price') %>
+
+ + +
+
+ +
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
<%- __('services.marketing.price') %>
+
+
+ + +
+
+ + +
+
+
+

+ <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> +

+

+ <%- __('portfolio.subtitle') %> +

+
+ +
+ <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> + <% featuredPortfolio.forEach((project, index) => { %> +
+
+ <% if (project.images && project.images.length > 0) { %> + <%= project.images.find(img => img.isPrimary)?.alt || project.title %> + <% } else { %> +
+ <%= project.title.charAt(0) %> +
+ <% } %> +
+
+
+ <% if (project.technologies && project.technologies.length > 0) { %> + <% project.technologies.slice(0, 3).forEach(tech => { %> + <%= tech %> + <% }) %> + <% } %> +
+
+
+
+
+ <%= project.category %> + + <%= project.viewCount || 0 %> + +
+

<%= project.title %>

+

<%= project.shortDescription || project.description %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% }) %> + <% } else { %> + +
+
+
+ E +
+
+
+
+ React + Node.js +
+
+
+
+
+ <%- __('portfolio.default.ecommerce') %> + + 1,234 + +
+

<%- __('portfolio.default.title') %>

+

<%- __('portfolio.default.description') %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% } %> +
+ + +
+
+ + +
+
+
+

+ <%- __('calculator.cta.title') %> +

+

+ <%- __('calculator.cta.subtitle') %> +

+ + + <%- __('calculator.cta.button') %> + +
+
+
+ + +
+
+
+
+

+ <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> +

+

+ <%- __('contact.cta.subtitle') %> +

+
+
+
+ +
+
+
<%- __('contact.phone.title') %>
+
<%- __('contact.phone.number') %>
+
+
+
+
+ +
+
+
<%- __('contact.email.title') %>
+
<%- __('contact.email.address') %>
+
+
+
+
+ +
+
+
<%- __('contact.telegram.title') %>
+
<%- __('contact.telegram.subtitle') %>
+
+
+
+
+
+
+

<%- __('contact.form.title') %>

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/.history/views/index_20251025214103.ejs b/.history/views/index_20251025214103.ejs new file mode 100644 index 0000000..bacbd6d --- /dev/null +++ b/.history/views/index_20251025214103.ejs @@ -0,0 +1,316 @@ + + + +
+
+
+ + +
+
+
+
+
+ +
+
+

+ <%- __('hero.title.smart') %> + <%- __('hero.title.solutions') %> +

+

+ <%- __('hero.subtitle') %> +

+ +
+ + +
+ + + +
+
+
+ + +
+
+
+

+ <%- __('services.title.our') %> <%- __('services.title.services') %> +

+

+ <%- __('services.subtitle') %> +

+
+ +
+ +
+
+ +
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
<%- __('services.web.price') %>
+
+ + +
+
+ +
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
<%- __('services.mobile.price') %>
+
+ + +
+
+ +
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
<%- __('services.design.price') %>
+
+ + +
+
+ +
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
<%- __('services.marketing.price') %>
+
+
+ + +
+
+ + +
+
+
+

+ <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> +

+

+ <%- __('portfolio.subtitle') %> +

+
+ +
+ <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> + <% featuredPortfolio.forEach((project, index) => { %> +
+
+ <% if (project.images && project.images.length > 0) { %> + <%= project.images.find(img => img.isPrimary)?.alt || project.title %> + <% } else { %> +
+ <%= project.title.charAt(0) %> +
+ <% } %> +
+
+
+ <% if (project.technologies && project.technologies.length > 0) { %> + <% project.technologies.slice(0, 3).forEach(tech => { %> + <%= tech %> + <% }) %> + <% } %> +
+
+
+
+
+ <%= project.category %> + + <%= project.viewCount || 0 %> + +
+

<%= project.title %>

+

<%= project.shortDescription || project.description %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% }) %> + <% } else { %> + +
+
+
+ E +
+
+
+
+ React + Node.js +
+
+
+
+
+ <%- __('portfolio.default.ecommerce') %> + + 1,234 + +
+

<%- __('portfolio.default.title') %>

+

<%- __('portfolio.default.description') %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% } %> +
+ + +
+
+ + +
+
+
+

+ <%- __('calculator.cta.title') %> +

+

+ <%- __('calculator.cta.subtitle') %> +

+ + + <%- __('calculator.cta.button') %> + +
+
+
+ + +
+
+
+
+

+ <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> +

+

+ <%- __('contact.cta.subtitle') %> +

+
+
+
+ +
+
+
<%- __('contact.phone.title') %>
+
<%- __('contact.phone.number') %>
+
+
+
+
+ +
+
+
<%- __('contact.email.title') %>
+
<%- __('contact.email.address') %>
+
+
+
+
+ +
+
+
<%- __('contact.telegram.title') %>
+
<%- __('contact.telegram.subtitle') %>
+
+
+
+
+
+
+

<%- __('contact.form.title') %>

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/.history/views/index_20251025214115.ejs b/.history/views/index_20251025214115.ejs new file mode 100644 index 0000000..e37b036 --- /dev/null +++ b/.history/views/index_20251025214115.ejs @@ -0,0 +1,316 @@ + + + +
+
+
+ + +
+
+
+
+
+ +
+
+

+ <%- __('hero.title.smart') %> + <%- __('hero.title.solutions') %> +

+

+ <%- __('hero.subtitle') %> +

+ +
+ + +
+ + + +
+
+
+ + +
+
+
+

+ <%- __('services.title.our') %> <%- __('services.title.services') %> +

+

+ <%- __('services.subtitle') %> +

+
+ +
+ +
+
+ +
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
<%- __('services.web.price') %>
+
+ + +
+
+ +
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
<%- __('services.mobile.price') %>
+
+ + +
+
+ +
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
<%- __('services.design.price') %>
+
+ + +
+
+ +
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
<%- __('services.marketing.price') %>
+
+
+ + +
+
+ + +
+
+
+

+ <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> +

+

+ <%- __('portfolio.subtitle') %> +

+
+ +
+ <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> + <% featuredPortfolio.forEach((project, index) => { %> +
+
+ <% if (project.images && project.images.length > 0) { %> + <%= project.images.find(img => img.isPrimary)?.alt || project.title %> + <% } else { %> +
+ <%= project.title.charAt(0) %> +
+ <% } %> +
+
+
+ <% if (project.technologies && project.technologies.length > 0) { %> + <% project.technologies.slice(0, 3).forEach(tech => { %> + <%= tech %> + <% }) %> + <% } %> +
+
+
+
+
+ <%= project.category %> + + <%= project.viewCount || 0 %> + +
+

<%= project.title %>

+

<%= project.shortDescription || project.description %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% }) %> + <% } else { %> + +
+
+
+ E +
+
+
+
+ React + Node.js +
+
+
+
+
+ <%- __('portfolio.default.ecommerce') %> + + 1,234 + +
+

<%- __('portfolio.default.title') %>

+

<%- __('portfolio.default.description') %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% } %> +
+ + +
+
+ + +
+
+
+

+ <%- __('calculator.cta.title') %> +

+

+ <%- __('calculator.cta.subtitle') %> +

+ + + <%- __('calculator.cta.button') %> + +
+
+
+ + +
+
+
+
+

+ <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> +

+

+ <%- __('contact.cta.subtitle') %> +

+
+
+
+ +
+
+
<%- __('contact.phone.title') %>
+
<%- __('contact.phone.number') %>
+
+
+
+
+ +
+
+
<%- __('contact.email.title') %>
+
<%- __('contact.email.address') %>
+
+
+
+
+ +
+
+
<%- __('contact.telegram.title') %>
+
<%- __('contact.telegram.subtitle') %>
+
+
+
+
+
+
+

<%- __('contact.form.title') %>

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/.history/views/index_20251025214128.ejs b/.history/views/index_20251025214128.ejs new file mode 100644 index 0000000..580f530 --- /dev/null +++ b/.history/views/index_20251025214128.ejs @@ -0,0 +1,316 @@ + + + +
+
+
+ + +
+
+
+
+
+ +
+
+

+ <%- __('hero.title.smart') %> + <%- __('hero.title.solutions') %> +

+

+ <%- __('hero.subtitle') %> +

+ +
+ + +
+ + + +
+
+
+ + +
+
+
+

+ <%- __('services.title.our') %> <%- __('services.title.services') %> +

+

+ <%- __('services.subtitle') %> +

+
+ +
+ +
+
+ +
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
<%- __('services.web.price') %>
+
+ + +
+
+ +
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
<%- __('services.mobile.price') %>
+
+ + +
+
+ +
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
<%- __('services.design.price') %>
+
+ + +
+
+ +
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
<%- __('services.marketing.price') %>
+
+
+ + +
+
+ + +
+
+
+

+ <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> +

+

+ <%- __('portfolio.subtitle') %> +

+
+ +
+ <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> + <% featuredPortfolio.forEach((project, index) => { %> +
+
+ <% if (project.images && project.images.length > 0) { %> + <%= project.images.find(img => img.isPrimary)?.alt || project.title %> + <% } else { %> +
+ <%= project.title.charAt(0) %> +
+ <% } %> +
+
+
+ <% if (project.technologies && project.technologies.length > 0) { %> + <% project.technologies.slice(0, 3).forEach(tech => { %> + <%= tech %> + <% }) %> + <% } %> +
+
+
+
+
+ <%= project.category %> + + <%= project.viewCount || 0 %> + +
+

<%= project.title %>

+

<%= project.shortDescription || project.description %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% }) %> + <% } else { %> + +
+
+
+ E +
+
+
+
+ React + Node.js +
+
+
+
+
+ <%- __('portfolio.default.ecommerce') %> + + 1,234 + +
+

<%- __('portfolio.default.title') %>

+

<%- __('portfolio.default.description') %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% } %> +
+ + +
+
+ + +
+
+
+

+ <%- __('calculator.cta.title') %> +

+

+ <%- __('calculator.cta.subtitle') %> +

+ + + <%- __('calculator.cta.button') %> + +
+
+
+ + +
+
+
+
+

+ <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> +

+

+ <%- __('contact.cta.subtitle') %> +

+
+
+
+ +
+
+
<%- __('contact.phone.title') %>
+
<%- __('contact.phone.number') %>
+
+
+
+
+ +
+
+
<%- __('contact.email.title') %>
+
<%- __('contact.email.address') %>
+
+
+
+
+ +
+
+
<%- __('contact.telegram.title') %>
+
<%- __('contact.telegram.subtitle') %>
+
+
+
+
+
+
+

<%- __('contact.form.title') %>

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/.history/views/index_20251025214219.ejs b/.history/views/index_20251025214219.ejs new file mode 100644 index 0000000..580f530 --- /dev/null +++ b/.history/views/index_20251025214219.ejs @@ -0,0 +1,316 @@ + + + +
+
+
+ + +
+
+
+
+
+ +
+
+

+ <%- __('hero.title.smart') %> + <%- __('hero.title.solutions') %> +

+

+ <%- __('hero.subtitle') %> +

+ +
+ + +
+ + + +
+
+
+ + +
+
+
+

+ <%- __('services.title.our') %> <%- __('services.title.services') %> +

+

+ <%- __('services.subtitle') %> +

+
+ +
+ +
+
+ +
+

<%- __('services.web.title') %>

+

<%- __('services.web.description') %>

+
<%- __('services.web.price') %>
+
+ + +
+
+ +
+

<%- __('services.mobile.title') %>

+

<%- __('services.mobile.description') %>

+
<%- __('services.mobile.price') %>
+
+ + +
+
+ +
+

<%- __('services.design.title') %>

+

<%- __('services.design.description') %>

+
<%- __('services.design.price') %>
+
+ + +
+
+ +
+

<%- __('services.marketing.title') %>

+

<%- __('services.marketing.description') %>

+
<%- __('services.marketing.price') %>
+
+
+ + +
+
+ + +
+
+
+

+ <%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %> +

+

+ <%- __('portfolio.subtitle') %> +

+
+ +
+ <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> + <% featuredPortfolio.forEach((project, index) => { %> +
+
+ <% if (project.images && project.images.length > 0) { %> + <%= project.images.find(img => img.isPrimary)?.alt || project.title %> + <% } else { %> +
+ <%= project.title.charAt(0) %> +
+ <% } %> +
+
+
+ <% if (project.technologies && project.technologies.length > 0) { %> + <% project.technologies.slice(0, 3).forEach(tech => { %> + <%= tech %> + <% }) %> + <% } %> +
+
+
+
+
+ <%= project.category %> + + <%= project.viewCount || 0 %> + +
+

<%= project.title %>

+

<%= project.shortDescription || project.description %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% }) %> + <% } else { %> + +
+
+
+ E +
+
+
+
+ React + Node.js +
+
+
+
+
+ <%- __('portfolio.default.ecommerce') %> + + 1,234 + +
+

<%- __('portfolio.default.title') %>

+

<%- __('portfolio.default.description') %>

+ + <%- __('common.view_details') %> + + + + +
+
+ <% } %> +
+ + +
+
+ + +
+
+
+

+ <%- __('calculator.cta.title') %> +

+

+ <%- __('calculator.cta.subtitle') %> +

+ + + <%- __('calculator.cta.button') %> + +
+
+
+ + +
+
+
+
+

+ <%- __('contact.cta.ready') %> <%- __('contact.cta.start') %><%- __('contact.cta.question') %> +

+

+ <%- __('contact.cta.subtitle') %> +

+
+
+
+ +
+
+
<%- __('contact.phone.title') %>
+
<%- __('contact.phone.number') %>
+
+
+
+
+ +
+
+
<%- __('contact.email.title') %>
+
<%- __('contact.email.address') %>
+
+
+
+
+ +
+
+
<%- __('contact.telegram.title') %>
+
<%- __('contact.telegram.subtitle') %>
+
+
+
+
+
+
+

<%- __('contact.form.title') %>

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/.history/views/layout_20251019161025.ejs b/.history/views/layout_20251019161025.ejs deleted file mode 100644 index a1a4954..0000000 --- a/.history/views/layout_20251019161025.ejs +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - <%= title %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
- <%- body %> -
- - - <%- include('partials/footer') %> - - - - - - - - - - <% if (settings && settings.seo && settings.seo.googleAnalytics) { %> - - - - <% } %> - - \ No newline at end of file diff --git a/.history/views/layout_20251019162544.ejs b/.history/views/layout_20251019162544.ejs deleted file mode 100644 index a1a4954..0000000 --- a/.history/views/layout_20251019162544.ejs +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - <%= title %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
- <%- body %> -
- - - <%- include('partials/footer') %> - - - - - - - - - - <% if (settings && settings.seo && settings.seo.googleAnalytics) { %> - - - - <% } %> - - \ No newline at end of file diff --git a/.history/views/layout_20251020045220.ejs b/.history/views/layout_20251020045220.ejs deleted file mode 100644 index 66704be..0000000 --- a/.history/views/layout_20251020045220.ejs +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - <%= title %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
- <%- body %> -
- - - <%- include('partials/footer') %> - - - - - - - - - - <% if (settings && settings.seo && settings.seo.googleAnalytics) { %> - - - - <% } %> - - \ No newline at end of file diff --git a/.history/views/layout_20251020045222.ejs b/.history/views/layout_20251020045222.ejs deleted file mode 100644 index 66704be..0000000 --- a/.history/views/layout_20251020045222.ejs +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - <%= title %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
- <%- body %> -
- - - <%- include('partials/footer') %> - - - - - - - - - - <% if (settings && settings.seo && settings.seo.googleAnalytics) { %> - - - - <% } %> - - \ No newline at end of file diff --git a/.history/views/layout_20251020045457.ejs b/.history/views/layout_20251020045457.ejs deleted file mode 100644 index 93d1e0b..0000000 --- a/.history/views/layout_20251020045457.ejs +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - <%= title %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
- <%- body %> -
- - - <%- include('partials/footer') %> - - - - - - - - - - <% if (settings && settings.seo && settings.seo.googleAnalytics) { %> - - - - <% } %> - - \ No newline at end of file diff --git a/.history/views/layout_20251020041359.ejs b/.history/views/layout_20251025213034.ejs similarity index 88% rename from .history/views/layout_20251020041359.ejs rename to .history/views/layout_20251025213034.ejs index 0a84c58..7e16cab 100644 --- a/.history/views/layout_20251020041359.ejs +++ b/.history/views/layout_20251025213034.ejs @@ -33,16 +33,23 @@ - + - - - + + + + + + + + + + - - + + @@ -86,5 +93,8 @@ gtag('config', '<%= settings.seo.googleAnalytics %>'); <% } %> + + + <%- script %> \ No newline at end of file diff --git a/.history/views/layout_20251020041711.ejs b/.history/views/layout_20251025213357.ejs similarity index 88% rename from .history/views/layout_20251020041711.ejs rename to .history/views/layout_20251025213357.ejs index 0a84c58..7e16cab 100644 --- a/.history/views/layout_20251020041711.ejs +++ b/.history/views/layout_20251025213357.ejs @@ -33,16 +33,23 @@ - + - - - + + + + + + + + + + - - + + @@ -86,5 +93,8 @@ gtag('config', '<%= settings.seo.googleAnalytics %>'); <% } %> + + + <%- script %> \ No newline at end of file diff --git a/.history/views/layout_20251020041353.ejs b/.history/views/layout_20251026092319.ejs similarity index 85% rename from .history/views/layout_20251020041353.ejs rename to .history/views/layout_20251026092319.ejs index 64bab5e..0d9badf 100644 --- a/.history/views/layout_20251020041353.ejs +++ b/.history/views/layout_20251026092319.ejs @@ -33,18 +33,26 @@ - + - - - + + + + + + + + + + - - + + + - + <%- include('partials/navigation') %> @@ -86,5 +94,8 @@ gtag('config', '<%= settings.seo.googleAnalytics %>'); <% } %> + + + <%- script %> \ No newline at end of file diff --git a/.history/views/layout_20251020041345.ejs b/.history/views/layout_20251026092353.ejs similarity index 83% rename from .history/views/layout_20251020041345.ejs rename to .history/views/layout_20251026092353.ejs index 0f39444..0d9badf 100644 --- a/.history/views/layout_20251020041345.ejs +++ b/.history/views/layout_20251026092353.ejs @@ -1,5 +1,5 @@ - + @@ -33,18 +33,26 @@ - + - - - + + + + + + + + + + - - + + + - + <%- include('partials/navigation') %> @@ -86,5 +94,8 @@ gtag('config', '<%= settings.seo.googleAnalytics %>'); <% } %> + + + <%- script %> \ No newline at end of file diff --git a/.history/views/layout_20251020045413.ejs b/.history/views/layout_20251026093506.ejs similarity index 83% rename from .history/views/layout_20251020045413.ejs rename to .history/views/layout_20251026093506.ejs index 93d1e0b..358014e 100644 --- a/.history/views/layout_20251020045413.ejs +++ b/.history/views/layout_20251026093506.ejs @@ -33,10 +33,10 @@ - + - - + + @@ -48,24 +48,10 @@ + + - - + @@ -109,5 +95,8 @@ gtag('config', '<%= settings.seo.googleAnalytics %>'); <% } %> + + + <%- script %> \ No newline at end of file diff --git a/.history/views/layout_20251026093547.ejs b/.history/views/layout_20251026093547.ejs new file mode 100644 index 0000000..358014e --- /dev/null +++ b/.history/views/layout_20251026093547.ejs @@ -0,0 +1,102 @@ + + + + + + <%= title %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%- include('partials/navigation') %> + + +
+ <%- body %> +
+ + + <%- include('partials/footer') %> + + + + + + + + + + <% if (settings && settings.seo && settings.seo.googleAnalytics) { %> + + + + <% } %> + + + <%- script %> + + \ No newline at end of file diff --git a/.history/views/partials/footer-new_20251019174149.ejs b/.history/views/partials/footer-new_20251019174149.ejs deleted file mode 100644 index 7200dad..0000000 --- a/.history/views/partials/footer-new_20251019174149.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer-new_20251019174206.ejs b/.history/views/partials/footer-new_20251019174206.ejs deleted file mode 100644 index 7200dad..0000000 --- a/.history/views/partials/footer-new_20251019174206.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251019161120.ejs b/.history/views/partials/footer_20251019161120.ejs deleted file mode 100644 index fb5f61d..0000000 --- a/.history/views/partials/footer_20251019161120.ejs +++ /dev/null @@ -1,103 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251019162544.ejs b/.history/views/partials/footer_20251019162544.ejs deleted file mode 100644 index fb5f61d..0000000 --- a/.history/views/partials/footer_20251019162544.ejs +++ /dev/null @@ -1,103 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020035751.ejs b/.history/views/partials/footer_20251020035751.ejs deleted file mode 100644 index 8094e67..0000000 --- a/.history/views/partials/footer_20251020035751.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020035803.ejs b/.history/views/partials/footer_20251020035803.ejs deleted file mode 100644 index 3efd1d7..0000000 --- a/.history/views/partials/footer_20251020035803.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020035824.ejs b/.history/views/partials/footer_20251020035824.ejs deleted file mode 100644 index 7a57269..0000000 --- a/.history/views/partials/footer_20251020035824.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020035835.ejs b/.history/views/partials/footer_20251020035835.ejs deleted file mode 100644 index d3d422c..0000000 --- a/.history/views/partials/footer_20251020035835.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020035857.ejs b/.history/views/partials/footer_20251020035857.ejs deleted file mode 100644 index d3d422c..0000000 --- a/.history/views/partials/footer_20251020035857.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020040336.ejs b/.history/views/partials/footer_20251020040336.ejs deleted file mode 100644 index e053a41..0000000 --- a/.history/views/partials/footer_20251020040336.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020040347.ejs b/.history/views/partials/footer_20251020040347.ejs deleted file mode 100644 index c660555..0000000 --- a/.history/views/partials/footer_20251020040347.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020040409.ejs b/.history/views/partials/footer_20251020040409.ejs deleted file mode 100644 index d7da06b..0000000 --- a/.history/views/partials/footer_20251020040409.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020040420.ejs b/.history/views/partials/footer_20251020040420.ejs deleted file mode 100644 index 1e0ae0a..0000000 --- a/.history/views/partials/footer_20251020040420.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/footer_20251020040538.ejs b/.history/views/partials/footer_20251020040538.ejs deleted file mode 100644 index 1e0ae0a..0000000 --- a/.history/views/partials/footer_20251020040538.ejs +++ /dev/null @@ -1,125 +0,0 @@ - \ No newline at end of file diff --git a/.history/views/partials/navigation-new_20251019172101.ejs b/.history/views/partials/navigation-new_20251019172101.ejs deleted file mode 100644 index fc18f45..0000000 --- a/.history/views/partials/navigation-new_20251019172101.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation-new_20251019173728.ejs b/.history/views/partials/navigation-new_20251019173728.ejs deleted file mode 100644 index fc18f45..0000000 --- a/.history/views/partials/navigation-new_20251019173728.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251019161046.ejs b/.history/views/partials/navigation_20251019161046.ejs deleted file mode 100644 index 4bf6bde..0000000 --- a/.history/views/partials/navigation_20251019161046.ejs +++ /dev/null @@ -1,80 +0,0 @@ - - - -
\ No newline at end of file diff --git a/.history/views/partials/navigation_20251019162544.ejs b/.history/views/partials/navigation_20251019162544.ejs deleted file mode 100644 index 4bf6bde..0000000 --- a/.history/views/partials/navigation_20251019162544.ejs +++ /dev/null @@ -1,80 +0,0 @@ - - - -
\ No newline at end of file diff --git a/.history/views/partials/navigation_20251020035643.ejs b/.history/views/partials/navigation_20251020035643.ejs deleted file mode 100644 index e894b95..0000000 --- a/.history/views/partials/navigation_20251020035643.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020035702.ejs b/.history/views/partials/navigation_20251020035702.ejs deleted file mode 100644 index 4a98f1e..0000000 --- a/.history/views/partials/navigation_20251020035702.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020035714.ejs b/.history/views/partials/navigation_20251020035714.ejs deleted file mode 100644 index bd3883c..0000000 --- a/.history/views/partials/navigation_20251020035714.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020035734.ejs b/.history/views/partials/navigation_20251020035734.ejs deleted file mode 100644 index 89f8511..0000000 --- a/.history/views/partials/navigation_20251020035734.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020035857.ejs b/.history/views/partials/navigation_20251020035857.ejs deleted file mode 100644 index 89f8511..0000000 --- a/.history/views/partials/navigation_20251020035857.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020040238.ejs b/.history/views/partials/navigation_20251020040238.ejs deleted file mode 100644 index dc6bee2..0000000 --- a/.history/views/partials/navigation_20251020040238.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020040256.ejs b/.history/views/partials/navigation_20251020040256.ejs deleted file mode 100644 index 73ec4de..0000000 --- a/.history/views/partials/navigation_20251020040256.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020040308.ejs b/.history/views/partials/navigation_20251020040308.ejs deleted file mode 100644 index df8c1e4..0000000 --- a/.history/views/partials/navigation_20251020040308.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020040327.ejs b/.history/views/partials/navigation_20251020040327.ejs deleted file mode 100644 index 3cf4b8b..0000000 --- a/.history/views/partials/navigation_20251020040327.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251020040538.ejs b/.history/views/partials/navigation_20251020040538.ejs deleted file mode 100644 index 3cf4b8b..0000000 --- a/.history/views/partials/navigation_20251020040538.ejs +++ /dev/null @@ -1,207 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251021173840.ejs b/.history/views/partials/navigation_20251021173840.ejs deleted file mode 100644 index fe1930e..0000000 --- a/.history/views/partials/navigation_20251021173840.ejs +++ /dev/null @@ -1,181 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251021173853.ejs b/.history/views/partials/navigation_20251021173853.ejs deleted file mode 100644 index e4043e7..0000000 --- a/.history/views/partials/navigation_20251021173853.ejs +++ /dev/null @@ -1,174 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251021173904.ejs b/.history/views/partials/navigation_20251021173904.ejs deleted file mode 100644 index b1f4b77..0000000 --- a/.history/views/partials/navigation_20251021173904.ejs +++ /dev/null @@ -1,157 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251021173947.ejs b/.history/views/partials/navigation_20251021173947.ejs deleted file mode 100644 index b1f4b77..0000000 --- a/.history/views/partials/navigation_20251021173947.ejs +++ /dev/null @@ -1,157 +0,0 @@ - - - \ No newline at end of file diff --git a/.history/views/partials/navigation_20251021185230.ejs b/.history/views/partials/navigation_20251026074853.ejs similarity index 86% rename from .history/views/partials/navigation_20251021185230.ejs rename to .history/views/partials/navigation_20251026074853.ejs index 977e552..ef2afa9 100644 --- a/.history/views/partials/navigation_20251021185230.ejs +++ b/.history/views/partials/navigation_20251026074853.ejs @@ -36,12 +36,20 @@
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251019163806.ejs b/.history/views/portfolio-detail_20251019163806.ejs deleted file mode 100644 index 4b3a40b..0000000 --- a/.history/views/portfolio-detail_20251019163806.ejs +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251019164427.ejs b/.history/views/portfolio-detail_20251019164427.ejs deleted file mode 100644 index b98c672..0000000 --- a/.history/views/portfolio-detail_20251019164427.ejs +++ /dev/null @@ -1,472 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251019165556.ejs b/.history/views/portfolio-detail_20251019165556.ejs deleted file mode 100644 index b98c672..0000000 --- a/.history/views/portfolio-detail_20251019165556.ejs +++ /dev/null @@ -1,472 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251019171031.ejs b/.history/views/portfolio-detail_20251019171031.ejs deleted file mode 100644 index 9cdc864..0000000 --- a/.history/views/portfolio-detail_20251019171031.ejs +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251019171203.ejs b/.history/views/portfolio-detail_20251019171203.ejs deleted file mode 100644 index 9cdc864..0000000 --- a/.history/views/portfolio-detail_20251019171203.ejs +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251020041641.ejs b/.history/views/portfolio-detail_20251020041641.ejs deleted file mode 100644 index 8dc3167..0000000 --- a/.history/views/portfolio-detail_20251020041641.ejs +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251020041649.ejs b/.history/views/portfolio-detail_20251020041649.ejs deleted file mode 100644 index 2b97f47..0000000 --- a/.history/views/portfolio-detail_20251020041649.ejs +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251020041655.ejs b/.history/views/portfolio-detail_20251020041655.ejs deleted file mode 100644 index 01a16ef..0000000 --- a/.history/views/portfolio-detail_20251020041655.ejs +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio-detail_20251020041711.ejs b/.history/views/portfolio-detail_20251020041711.ejs deleted file mode 100644 index 01a16ef..0000000 --- a/.history/views/portfolio-detail_20251020041711.ejs +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - <%= portfolio.title %> - SmartSolTech 포트폴리오 - - - - - - - - - - <% if (portfolio.images && portfolio.images.length > 0) { %> - - <% } %> - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
- - - -
- -
-
- - <%= getCategoryName(portfolio.category) %> - - <% if (portfolio.featured) { %> - - FEATURED - - <% } %> - <% if (portfolio.status === 'completed') { %> - - 완료 - - <% } else if (portfolio.status === 'in-progress') { %> - - 진행 중 - - <% } %> -
- -

- <%= portfolio.title %> -

- -

- <%= portfolio.description %> -

- - -
-
-
<%= portfolio.viewCount || 0 %>
-
조회수
-
-
-
<%= portfolio.likes || 0 %>
-
좋아요
-
-
-
- <%= portfolio.completedAt ? new Date(portfolio.completedAt).getFullYear() : new Date().getFullYear() %> -
-
완료년도
-
-
- - -
- <% if (portfolio.projectUrl) { %> - - - 프로젝트 보기 - - <% } %> - -
-
- - -
- <% if (portfolio.images && portfolio.images.length > 0) { %> -
- <%= portfolio.title %> -
-
- <% } else { %> -
- -
- <% } %> -
-
-
-
- - -
-
-
- - -
- - - <% if (portfolio.images && portfolio.images.length > 1) { %> -
-

프로젝트 갤러리

- - -
-
- <% portfolio.images.forEach(image => { %> -
-
- <%= image.alt || portfolio.title %> -
-

<%= image.alt || portfolio.title %>

-
-
-
- <% }) %> -
-
-
-
-
-
- <% } %> - - -
-

프로젝트 개요

-
- <%= portfolio.description %> -
-
- - - <% if (portfolio.technologies && portfolio.technologies.length > 0) { %> -
-

사용된 기술

-
- <% portfolio.technologies.forEach(tech => { %> -
-
<%= tech %>
-
- <% }) %> -
-
- <% } %> - - -
-

이 프로젝트 공유하기

-
- - - - -
-
-
- - -
- - -
-

프로젝트 정보

- -
- <% if (portfolio.clientName) { %> -
-
클라이언트
-
<%= portfolio.clientName %>
-
- <% } %> - -
-
카테고리
-
<%= getCategoryName(portfolio.category) %>
-
- - <% if (portfolio.completedAt) { %> -
-
완료일
-
- <%= new Date(portfolio.completedAt).toLocaleDateString('ko-KR') %> -
-
- <% } %> - - <% if (portfolio.projectUrl) { %> -
-
프로젝트 URL
- - <%= portfolio.projectUrl %> - -
- <% } %> -
-
- - -
-

비슷한 프로젝트가 필요하신가요?

-

- 이런 프로젝트에 관심이 있으시다면 언제든 연락주세요. - 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다. -

- -
- -
-
-
-
- - -
-
-
-

- 관련 프로젝트 -

-

- 비슷한 카테고리의 다른 프로젝트들도 확인해보세요 -

-
- -
- <% if (relatedProjects && relatedProjects.length > 0) { %> - <% relatedProjects.forEach((project, index) => { %> -
- -
- <% if (project.images && project.images.length > 0) { %> - <%= project.title %> - <% } else { %> -
- -
- <% } %> -
- -
-

- <%= project.title %> -

-

- <%= project.shortDescription || project.description %> -

- - 자세히 보기 → - -
-
- <% }) %> - <% } else { %> -
-

관련 프로젝트가 없습니다.

-
- <% } %> -
-
-
- - <%- include('partials/footer') %> - - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251019162820.ejs b/.history/views/portfolio_20251019162820.ejs deleted file mode 100644 index 5d868dc..0000000 --- a/.history/views/portfolio_20251019162820.ejs +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - 포트폴리오 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251019163806.ejs b/.history/views/portfolio_20251019163806.ejs deleted file mode 100644 index 5d868dc..0000000 --- a/.history/views/portfolio_20251019163806.ejs +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - 포트폴리오 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251019164448.ejs b/.history/views/portfolio_20251019164448.ejs deleted file mode 100644 index 8173622..0000000 --- a/.history/views/portfolio_20251019164448.ejs +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - 포트폴리오 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251019165556.ejs b/.history/views/portfolio_20251019165556.ejs deleted file mode 100644 index 8173622..0000000 --- a/.history/views/portfolio_20251019165556.ejs +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - 포트폴리오 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251020041534.ejs b/.history/views/portfolio_20251020041534.ejs deleted file mode 100644 index de68188..0000000 --- a/.history/views/portfolio_20251020041534.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - 포트폴리오 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251020041542.ejs b/.history/views/portfolio_20251020041542.ejs deleted file mode 100644 index 56d5684..0000000 --- a/.history/views/portfolio_20251020041542.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.title') || '포트폴리오 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251020041547.ejs b/.history/views/portfolio_20251020041547.ejs deleted file mode 100644 index 9002a1e..0000000 --- a/.history/views/portfolio_20251020041547.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.title') || '포트폴리오 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251020041711.ejs b/.history/views/portfolio_20251020041711.ejs deleted file mode 100644 index 9002a1e..0000000 --- a/.history/views/portfolio_20251020041711.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.title') || '포트폴리오 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021184538.ejs b/.history/views/portfolio_20251021184538.ejs deleted file mode 100644 index d4eaa47..0000000 --- a/.history/views/portfolio_20251021184538.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021184547.ejs b/.history/views/portfolio_20251021184547.ejs deleted file mode 100644 index d4eaa47..0000000 --- a/.history/views/portfolio_20251021184547.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 포트폴리오 -

-

- 혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요 -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211541.ejs b/.history/views/portfolio_20251021211541.ejs deleted file mode 100644 index 0c29b0c..0000000 --- a/.history/views/portfolio_20251021211541.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211552.ejs b/.history/views/portfolio_20251021211552.ejs deleted file mode 100644 index f336ce8..0000000 --- a/.history/views/portfolio_20251021211552.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211553.ejs b/.history/views/portfolio_20251021211553.ejs deleted file mode 100644 index f336ce8..0000000 --- a/.history/views/portfolio_20251021211553.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211600.ejs b/.history/views/portfolio_20251021211600.ejs deleted file mode 100644 index bd6b34b..0000000 --- a/.history/views/portfolio_20251021211600.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211608.ejs b/.history/views/portfolio_20251021211608.ejs deleted file mode 100644 index cb596c4..0000000 --- a/.history/views/portfolio_20251021211608.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

아직 포트폴리오가 없습니다

-

곧 멋진 프로젝트들을 공개할 예정입니다!

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211616.ejs b/.history/views/portfolio_20251021211616.ejs deleted file mode 100644 index 02a3ef1..0000000 --- a/.history/views/portfolio_20251021211616.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211626.ejs b/.history/views/portfolio_20251021211626.ejs deleted file mode 100644 index ed6d86e..0000000 --- a/.history/views/portfolio_20251021211626.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- 다음 프로젝트의 주인공이 되어보세요 -

-

- 우리와 함께 혁신적인 디지털 솔루션을 만들어보세요 -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211634.ejs b/.history/views/portfolio_20251021211634.ejs deleted file mode 100644 index e999ab4..0000000 --- a/.history/views/portfolio_20251021211634.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211644.ejs b/.history/views/portfolio_20251021211644.ejs deleted file mode 100644 index 2957682..0000000 --- a/.history/views/portfolio_20251021211644.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211654.ejs b/.history/views/portfolio_20251021211654.ejs deleted file mode 100644 index c6f1001..0000000 --- a/.history/views/portfolio_20251021211654.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // 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; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211703.ejs b/.history/views/portfolio_20251021211703.ejs deleted file mode 100644 index b9fae10..0000000 --- a/.history/views/portfolio_20251021211703.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - FEATURED - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // Helper function for category names - uses i18n - function getCategoryName(category) { - const categoryMap = { - 'web-development': 'portfolio_page.categories.web-development', - 'mobile-app': 'portfolio_page.categories.mobile-app', - 'ui-ux-design': 'portfolio_page.categories.ui-ux-design', - 'branding': 'portfolio_page.categories.branding', - 'marketing': 'portfolio_page.categories.marketing' - }; - return categoryMap[category] ? __(categoryMap[category]) : category; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211710.ejs b/.history/views/portfolio_20251021211710.ejs deleted file mode 100644 index 98dedc6..0000000 --- a/.history/views/portfolio_20251021211710.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - <%- __('portfolio_page.labels.featured') %> - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // Helper function for category names - uses i18n - function getCategoryName(category) { - const categoryMap = { - 'web-development': 'portfolio_page.categories.web-development', - 'mobile-app': 'portfolio_page.categories.mobile-app', - 'ui-ux-design': 'portfolio_page.categories.ui-ux-design', - 'branding': 'portfolio_page.categories.branding', - 'marketing': 'portfolio_page.categories.marketing' - }; - return categoryMap[category] ? __(categoryMap[category]) : category; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021211719.ejs b/.history/views/portfolio_20251021211719.ejs deleted file mode 100644 index 98dedc6..0000000 --- a/.history/views/portfolio_20251021211719.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - <%- __('portfolio_page.labels.featured') %> - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // Helper function for category names - uses i18n - function getCategoryName(category) { - const categoryMap = { - 'web-development': 'portfolio_page.categories.web-development', - 'mobile-app': 'portfolio_page.categories.mobile-app', - 'ui-ux-design': 'portfolio_page.categories.ui-ux-design', - 'branding': 'portfolio_page.categories.branding', - 'marketing': 'portfolio_page.categories.marketing' - }; - return categoryMap[category] ? __(categoryMap[category]) : category; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021212403.ejs b/.history/views/portfolio_20251021212403.ejs deleted file mode 100644 index cabef5e..0000000 --- a/.history/views/portfolio_20251021212403.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - <%- __('portfolio_page.labels.featured') %> - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // Helper function for category names - uses i18n - function getCategoryName(category) { - const categoryMap = { - 'web-development': 'portfolio_page.categories.web-development', - 'mobile-app': 'portfolio_page.categories.mobile-app', - 'ui-ux-design': 'portfolio_page.categories.ui-ux-design', - 'branding': 'portfolio_page.categories.branding', - 'marketing': 'portfolio_page.categories.marketing' - }; - return categoryMap[category] ? __(categoryMap[category]) : category; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021212447.ejs b/.history/views/portfolio_20251021212447.ejs deleted file mode 100644 index 4a16fd9..0000000 --- a/.history/views/portfolio_20251021212447.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - <%- __('portfolio_page.labels.featured') %> - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // Helper function for category names - uses i18n - function getCategoryName(category) { - const categoryMap = { - 'web-development': 'portfolio_page.categories.web-development', - 'mobile-app': 'portfolio_page.categories.mobile-app', - 'ui-ux-design': 'portfolio_page.categories.ui-ux-design', - 'branding': 'portfolio_page.categories.branding', - 'marketing': 'portfolio_page.categories.marketing' - }; - return categoryMap[category] ? __(categoryMap[category]) : category; - } - %> - - \ No newline at end of file diff --git a/.history/views/portfolio_20251021212532.ejs b/.history/views/portfolio_20251021212532.ejs deleted file mode 100644 index 4a16fd9..0000000 --- a/.history/views/portfolio_20251021212532.ejs +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('portfolio_page.title') %> -

-

- <%- __('portfolio_page.subtitle') %> -

-
- - - - -
-
-
- - -
-
-
- <% if (portfolioItems && portfolioItems.length > 0) { %> - <% portfolioItems.forEach((item, index) => { %> -
- - -
- <% if (item.images && item.images.length > 0) { %> - <%= item.title %> - <% } else { %> -
- -
- <% } %> - - -
- - <%= getCategoryName(item.category) %> - -
- - - <% if (item.featured) { %> -
- - <%- __('portfolio_page.labels.featured') %> - -
- <% } %> - - - -
- - -
-

- <%= item.title %> -

-

- <%= item.shortDescription || item.description %> -

- - -
- <% if (item.technologies && item.technologies.length > 0) { %> - <% item.technologies.slice(0, 3).forEach(tech => { %> - - <%= tech %> - - <% }) %> - <% if (item.technologies.length > 3) { %> - - +<%= item.technologies.length - 3 %> - - <% } %> - <% } %> -
- - -
-
- <% if (item.clientName) { %> - - <%= item.clientName %> - <% } %> -
-
- - - <%= item.viewCount || 0 %> - - - - <%= item.likes || 0 %> - -
-
- - - -
-
- <% }) %> - <% } else { %> -
- -

<%- __('portfolio_page.empty.title') %>

-

<%- __('portfolio_page.empty.subtitle') %>

-
- <% } %> -
- - - <% if (portfolioItems && portfolioItems.length >= 9) { %> -
- -
- <% } %> -
-
- - -
-
-

- <%- __('portfolio_page.cta.title') %> -

-

- <%- __('portfolio_page.cta.subtitle') %> -

- -
-
- - <%- include('partials/footer') %> - - - - - - - - <% - // Helper function for category names - uses i18n - function getCategoryName(category) { - const categoryMap = { - 'web-development': 'portfolio_page.categories.web-development', - 'mobile-app': 'portfolio_page.categories.mobile-app', - 'ui-ux-design': 'portfolio_page.categories.ui-ux-design', - 'branding': 'portfolio_page.categories.branding', - 'marketing': 'portfolio_page.categories.marketing' - }; - return categoryMap[category] ? __(categoryMap[category]) : category; - } - %> - - \ No newline at end of file diff --git a/.history/views/services_20251019163647.ejs b/.history/views/services_20251019163647.ejs deleted file mode 100644 index 02f0191..0000000 --- a/.history/views/services_20251019163647.ejs +++ /dev/null @@ -1,293 +0,0 @@ - - - - - - 서비스 - SmartSolTech - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251019163807.ejs b/.history/views/services_20251019163807.ejs deleted file mode 100644 index 02f0191..0000000 --- a/.history/views/services_20251019163807.ejs +++ /dev/null @@ -1,293 +0,0 @@ - - - - - - 서비스 - SmartSolTech - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251020041507.ejs b/.history/views/services_20251020041507.ejs deleted file mode 100644 index 092e75b..0000000 --- a/.history/views/services_20251020041507.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - 서비스 - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251020041515.ejs b/.history/views/services_20251020041515.ejs deleted file mode 100644 index 3fc5b02..0000000 --- a/.history/views/services_20251020041515.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.title') || '서비스 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251020041521.ejs b/.history/views/services_20251020041521.ejs deleted file mode 100644 index 6e04bbe..0000000 --- a/.history/views/services_20251020041521.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.title') || '서비스 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251020041711.ejs b/.history/views/services_20251020041711.ejs deleted file mode 100644 index 6e04bbe..0000000 --- a/.history/views/services_20251020041711.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.title') || '서비스 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251020225327.ejs b/.history/views/services_20251020225327.ejs deleted file mode 100644 index c48265c..0000000 --- a/.history/views/services_20251020225327.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.title') || '서비스 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251020225419.ejs b/.history/views/services_20251020225419.ejs deleted file mode 100644 index c48265c..0000000 --- a/.history/views/services_20251020225419.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.title') || '서비스 - SmartSolTech' %> - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184120.ejs b/.history/views/services_20251021184120.ejs deleted file mode 100644 index e7703cc..0000000 --- a/.history/views/services_20251021184120.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184308.ejs b/.history/views/services_20251021184308.ejs deleted file mode 100644 index e7703cc..0000000 --- a/.history/views/services_20251021184308.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- 우리의 서비스 -

-

- 혁신적인 기술로 비즈니스의 성장을 지원합니다 -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184341.ejs b/.history/views/services_20251021184341.ejs deleted file mode 100644 index 5d933e7..0000000 --- a/.history/views/services_20251021184341.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184342.ejs b/.history/views/services_20251021184342.ejs deleted file mode 100644 index 5d933e7..0000000 --- a/.history/views/services_20251021184342.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
시작가격
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : '상담' %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184349.ejs b/.history/views/services_20251021184349.ejs deleted file mode 100644 index 78efb4f..0000000 --- a/.history/views/services_20251021184349.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184350.ejs b/.history/views/services_20251021184350.ejs deleted file mode 100644 index 78efb4f..0000000 --- a/.history/views/services_20251021184350.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184356.ejs b/.history/views/services_20251021184356.ejs deleted file mode 100644 index ccc8a17..0000000 --- a/.history/views/services_20251021184356.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184357.ejs b/.history/views/services_20251021184357.ejs deleted file mode 100644 index ccc8a17..0000000 --- a/.history/views/services_20251021184357.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184402.ejs b/.history/views/services_20251021184402.ejs deleted file mode 100644 index 30f0b04..0000000 --- a/.history/views/services_20251021184402.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - 인기 - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184408.ejs b/.history/views/services_20251021184408.ejs deleted file mode 100644 index 02a7863..0000000 --- a/.history/views/services_20251021184408.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - <%- __('services.cards.popular') %> - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

서비스 준비 중

-

곧 다양한 서비스를 제공할 예정입니다!

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251021184415.ejs b/.history/views/services_20251021184415.ejs deleted file mode 100644 index 0947a3e..0000000 --- a/.history/views/services_20251021184415.ejs +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-

- <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> -

-

- <%- __('services.hero.subtitle') %> -

-
-
- - -
-
-
- <% if (services && services.length > 0) { %> - <% services.forEach((service, index) => { %> -
- - -
- -
- - -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

- - - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ - <% } %> -
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %> -
- <% } %> - - - - - - <% if (service.featured) { %> -
- - <%- __('services.cards.popular') %> - -
- <% } %> -
- <% }) %> - <% } else { %> -
- -

<%- __('services.cards.coming_soon') %>

-

<%- __('services.cards.coming_soon_desc') %>

-
- <% } %> -
-
-
- - -
-
-
-

- 프로젝트 진행 과정 -

-

- 체계적이고 전문적인 프로세스로 프로젝트를 진행합니다 -

-
- -
- -
-
- 1 -
-

상담 및 기획

-

- 고객의 요구사항을 정확히 파악하고 - 최적의 솔루션을 기획합니다 -

-
- - -
-
- 2 -
-

디자인 및 설계

-

- 사용자 중심의 직관적인 디자인과 - 견고한 시스템 아키텍처를 설계합니다 -

-
- - -
-
- 3 -
-

개발 및 구현

-

- 최신 기술과 모범 사례를 활용하여 - 효율적이고 확장 가능한 솔루션을 개발합니다 -

-
- - -
-
- 4 -
-

테스트 및 배포

-

- 철저한 테스트를 통해 품질을 보장하고 - 안정적인 배포를 진행합니다 -

-
-
-
-
- - -
-
-
- -
-

- 왜 SmartSolTech를 선택해야 할까요? -

- -
- -
-
- -
-
-

최신 기술 활용

-

- 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 - 미래 지향적인 솔루션을 제공합니다. -

-
-
- - -
-
- -
-
-

전문가 팀

-

- 각 분야의 전문가들로 구성된 팀이 협력하여 - 최고 품질의 결과물을 보장합니다. -

-
-
- - -
-
- -
-
-

빠른 대응

-

- 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 - 정해진 일정 내에 프로젝트를 완료합니다. -

-
-
- - -
-
- -
-
-

지속적인 지원

-

- 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 - 통해 장기적인 파트너십을 유지합니다. -

-
-
-
-
- - -
-
-
- -

품질 보장

-

- 고객 만족을 위한
- 최고 품질의 서비스 -

-
-
-
-
-
-
- - -
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 -

- -
-
- - <%- include('partials/footer') %> - - - - - - \ No newline at end of file diff --git a/.history/views/services_20251025213936.ejs b/.history/views/services_20251025213936.ejs new file mode 100644 index 0000000..5f7fde8 --- /dev/null +++ b/.history/views/services_20251025213936.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025213946.ejs b/.history/views/services_20251025213946.ejs new file mode 100644 index 0000000..afc26cf --- /dev/null +++ b/.history/views/services_20251025213946.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025213954.ejs b/.history/views/services_20251025213954.ejs new file mode 100644 index 0000000..0af11a4 --- /dev/null +++ b/.history/views/services_20251025213954.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025214003.ejs b/.history/views/services_20251025214003.ejs new file mode 100644 index 0000000..53edc4b --- /dev/null +++ b/.history/views/services_20251025214003.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025214011.ejs b/.history/views/services_20251025214011.ejs new file mode 100644 index 0000000..08006d9 --- /dev/null +++ b/.history/views/services_20251025214011.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025214019.ejs b/.history/views/services_20251025214019.ejs new file mode 100644 index 0000000..356b965 --- /dev/null +++ b/.history/views/services_20251025214019.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025214029.ejs b/.history/views/services_20251025214029.ejs new file mode 100644 index 0000000..e4f0ea3 --- /dev/null +++ b/.history/views/services_20251025214029.ejs @@ -0,0 +1,313 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + + + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251021212411.ejs b/.history/views/services_20251025214038.ejs similarity index 61% rename from .history/views/services_20251021212411.ejs rename to .history/views/services_20251025214038.ejs index 4cf1f8d..3aeb3b6 100644 --- a/.history/views/services_20251021212411.ejs +++ b/.history/views/services_20251025214038.ejs @@ -1,29 +1,6 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - + - - - - <%- include('partials/navigation') %> +
@@ -40,70 +17,99 @@
-
+
<% if (services && services.length > 0) { %> <% services.forEach((service, index) => { %> -
+ data-aos-delay="<%= index * 150 %>"> - -
- -
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
- -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

+ +
+
+ + +
+ + <%= service.category %> + +
- - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
<% } %>
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %>
- <% } %> - - - - - - <% if (service.featured) { %> -
- - <%- __('services.cards.popular') %> - -
- <% } %>
<% }) %> <% } else { %> -
+

<%- __('services.cards.coming_soon') %>

<%- __('services.cards.coming_soon_desc') %>

@@ -120,7 +126,7 @@

<%- __('services.process.title') %>

-

+

<%- __('services.process.subtitle') %>

@@ -132,7 +138,7 @@ 1

상담 및 기획

-

+

고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다

@@ -144,7 +150,7 @@ 2

디자인 및 설계

-

+

사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다

@@ -156,7 +162,7 @@ 3

개발 및 구현

-

+

최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다

@@ -168,7 +174,7 @@ 4

테스트 및 배포

-

+

철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다

@@ -286,10 +292,7 @@
- <%- include('partials/footer') %> + - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/.history/views/services_20251021212532.ejs b/.history/views/services_20251025214219.ejs similarity index 61% rename from .history/views/services_20251021212532.ejs rename to .history/views/services_20251025214219.ejs index 4cf1f8d..3aeb3b6 100644 --- a/.history/views/services_20251021212532.ejs +++ b/.history/views/services_20251025214219.ejs @@ -1,29 +1,6 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - + - - - - <%- include('partials/navigation') %> +
@@ -40,70 +17,99 @@
-
+
<% if (services && services.length > 0) { %> <% services.forEach((service, index) => { %> -
+ data-aos-delay="<%= index * 150 %>"> - -
- -
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
- -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

+ +
+
+ + +
+ + <%= service.category %> + +
- - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
<% } %>
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %>
- <% } %> - - - - - - <% if (service.featured) { %> -
- - <%- __('services.cards.popular') %> - -
- <% } %>
<% }) %> <% } else { %> -
+

<%- __('services.cards.coming_soon') %>

<%- __('services.cards.coming_soon_desc') %>

@@ -120,7 +126,7 @@

<%- __('services.process.title') %>

-

+

<%- __('services.process.subtitle') %>

@@ -132,7 +138,7 @@ 1

상담 및 기획

-

+

고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다

@@ -144,7 +150,7 @@ 2

디자인 및 설계

-

+

사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다

@@ -156,7 +162,7 @@ 3

개발 및 구현

-

+

최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다

@@ -168,7 +174,7 @@ 4

테스트 및 배포

-

+

철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다

@@ -286,10 +292,7 @@
- <%- include('partials/footer') %> + - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/.history/views/services_20251025214415.ejs b/.history/views/services_20251025214415.ejs new file mode 100644 index 0000000..f7bda8e --- /dev/null +++ b/.history/views/services_20251025214415.ejs @@ -0,0 +1,298 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025214425.ejs b/.history/views/services_20251025214425.ejs new file mode 100644 index 0000000..f7bda8e --- /dev/null +++ b/.history/views/services_20251025214425.ejs @@ -0,0 +1,298 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

상담 및 기획

+

+ 고객의 요구사항을 정확히 파악하고 + 최적의 솔루션을 기획합니다 +

+
+ + +
+
+ 2 +
+

디자인 및 설계

+

+ 사용자 중심의 직관적인 디자인과 + 견고한 시스템 아키텍처를 설계합니다 +

+
+ + +
+
+ 3 +
+

개발 및 구현

+

+ 최신 기술과 모범 사례를 활용하여 + 효율적이고 확장 가능한 솔루션을 개발합니다 +

+
+ + +
+
+ 4 +
+

테스트 및 배포

+

+ 철저한 테스트를 통해 품질을 보장하고 + 안정적인 배포를 진행합니다 +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251021184446.ejs b/.history/views/services_20251025214753.ejs similarity index 61% rename from .history/views/services_20251021184446.ejs rename to .history/views/services_20251025214753.ejs index 4e9d2da..d977925 100644 --- a/.history/views/services_20251021184446.ejs +++ b/.history/views/services_20251025214753.ejs @@ -1,32 +1,9 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - + - - - - <%- include('partials/navigation') %> + -
+

<%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> @@ -40,70 +17,99 @@
-
+
<% if (services && services.length > 0) { %> <% services.forEach((service, index) => { %> -
+ data-aos-delay="<%= index * 150 %>"> - -
- -
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
- -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

+ +
+
+ + +
+ + <%= service.category %> + +
- - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
<% } %>
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %>
- <% } %> - - - - - - <% if (service.featured) { %> -
- - <%- __('services.cards.popular') %> - -
- <% } %>
<% }) %> <% } else { %> -
+

<%- __('services.cards.coming_soon') %>

<%- __('services.cards.coming_soon_desc') %>

@@ -120,7 +126,7 @@

<%- __('services.process.title') %>

-

+

<%- __('services.process.subtitle') %>

@@ -132,7 +138,7 @@ 1

상담 및 기획

-

+

고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다

@@ -144,7 +150,7 @@ 2

디자인 및 설계

-

+

사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다

@@ -156,7 +162,7 @@ 3

개발 및 구현

-

+

최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다

@@ -168,7 +174,7 @@ 4

테스트 및 배포

-

+

철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다

@@ -286,10 +292,7 @@
- <%- include('partials/footer') %> + - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/.history/views/services_20251021184425.ejs b/.history/views/services_20251025214837.ejs similarity index 61% rename from .history/views/services_20251021184425.ejs rename to .history/views/services_20251025214837.ejs index 4e9d2da..d977925 100644 --- a/.history/views/services_20251021184425.ejs +++ b/.history/views/services_20251025214837.ejs @@ -1,32 +1,9 @@ - - - - - - <%- __('services.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - + - - - - <%- include('partials/navigation') %> + -
+

<%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> @@ -40,70 +17,99 @@
-
+
<% if (services && services.length > 0) { %> <% services.forEach((service, index) => { %> -
+ data-aos-delay="<%= index * 150 %>"> - -
- -
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
- -

- <%= service.name %> -

- -

- <%= service.shortDescription || service.description %> -

+ +
+
+ + +
+ + <%= service.category %> + +
- - <% if (service.pricing) { %> -
-
<%- __('services.cards.starting_price') %>
-
- <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> - <% if (service.pricing.basePrice) { %> - 원~ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
<% } %>
- <% if (service.pricing.priceRange) { %> -
- <%= service.pricing.priceRange.min.toLocaleString() %>원 - - <%= service.pricing.priceRange.max.toLocaleString() %>원 -
- <% } %>
- <% } %> - - - - - - <% if (service.featured) { %> -
- - <%- __('services.cards.popular') %> - -
- <% } %>
<% }) %> <% } else { %> -
+

<%- __('services.cards.coming_soon') %>

<%- __('services.cards.coming_soon_desc') %>

@@ -120,7 +126,7 @@

<%- __('services.process.title') %>

-

+

<%- __('services.process.subtitle') %>

@@ -132,7 +138,7 @@ 1

상담 및 기획

-

+

고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다

@@ -144,7 +150,7 @@ 2

디자인 및 설계

-

+

사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다

@@ -156,7 +162,7 @@ 3

개발 및 구현

-

+

최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다

@@ -168,7 +174,7 @@ 4

테스트 및 배포

-

+

철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다

@@ -286,10 +292,7 @@
- <%- include('partials/footer') %> + - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/.history/views/services_20251025214958.ejs b/.history/views/services_20251025214958.ejs new file mode 100644 index 0000000..5300758 --- /dev/null +++ b/.history/views/services_20251025214958.ejs @@ -0,0 +1,294 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ 왜 SmartSolTech를 선택해야 할까요? +

+ +
+ +
+
+ +
+
+

최신 기술 활용

+

+ 항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 + 미래 지향적인 솔루션을 제공합니다. +

+
+
+ + +
+
+ +
+
+

전문가 팀

+

+ 각 분야의 전문가들로 구성된 팀이 협력하여 + 최고 품질의 결과물을 보장합니다. +

+
+
+ + +
+
+ +
+
+

빠른 대응

+

+ 신속한 커뮤니케이션과 효율적인 프로젝트 관리로 + 정해진 일정 내에 프로젝트를 완료합니다. +

+
+
+ + +
+
+ +
+
+

지속적인 지원

+

+ 프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 + 통해 장기적인 파트너십을 유지합니다. +

+
+
+
+
+ + +
+
+
+ +

품질 보장

+

+ 고객 만족을 위한
+ 최고 품질의 서비스 +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025215027.ejs b/.history/views/services_20251025215027.ejs new file mode 100644 index 0000000..9eebd3d --- /dev/null +++ b/.history/views/services_20251025215027.ejs @@ -0,0 +1,289 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ <%- __('services.why_choose.title') %> +

+ +
+ +
+
+ +
+
+

<%- __('services.why_choose.modern_tech.title') %>

+

+ <%- __('services.why_choose.modern_tech.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.expert_team.title') %>

+

+ <%- __('services.why_choose.expert_team.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.fast_response.title') %>

+

+ <%- __('services.why_choose.fast_response.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.continuous_support.title') %>

+

+ <%- __('services.why_choose.continuous_support.description') %> +

+
+
+
+
+ + +
+
+
+ +

<%- __('services.why_choose.quality_guarantee.title') %>

+

+ <%- __('services.why_choose.quality_guarantee.subtitle') %> +

+
+
+
+
+
+
+ + +
+
+

+ 프로젝트를 시작할 준비가 되셨나요? +

+

+ 무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다 +

+ +
+
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025215042.ejs b/.history/views/services_20251025215042.ejs new file mode 100644 index 0000000..cd5f3c5 --- /dev/null +++ b/.history/views/services_20251025215042.ejs @@ -0,0 +1,289 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ <%- __('services.why_choose.title') %> +

+ +
+ +
+
+ +
+
+

<%- __('services.why_choose.modern_tech.title') %>

+

+ <%- __('services.why_choose.modern_tech.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.expert_team.title') %>

+

+ <%- __('services.why_choose.expert_team.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.fast_response.title') %>

+

+ <%- __('services.why_choose.fast_response.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.continuous_support.title') %>

+

+ <%- __('services.why_choose.continuous_support.description') %> +

+
+
+
+
+ + +
+
+
+ +

<%- __('services.why_choose.quality_guarantee.title') %>

+

+ <%- __('services.why_choose.quality_guarantee.subtitle') %> +

+
+
+
+
+
+
+ + +
+ +
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025215055.ejs b/.history/views/services_20251025215055.ejs new file mode 100644 index 0000000..cd5f3c5 --- /dev/null +++ b/.history/views/services_20251025215055.ejs @@ -0,0 +1,289 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ <%- __('services.why_choose.title') %> +

+ +
+ +
+
+ +
+
+

<%- __('services.why_choose.modern_tech.title') %>

+

+ <%- __('services.why_choose.modern_tech.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.expert_team.title') %>

+

+ <%- __('services.why_choose.expert_team.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.fast_response.title') %>

+

+ <%- __('services.why_choose.fast_response.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.continuous_support.title') %>

+

+ <%- __('services.why_choose.continuous_support.description') %> +

+
+
+
+
+ + +
+
+
+ +

<%- __('services.why_choose.quality_guarantee.title') %>

+

+ <%- __('services.why_choose.quality_guarantee.subtitle') %> +

+
+
+
+
+
+
+ + +
+ +
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025215321.ejs b/.history/views/services_20251025215321.ejs new file mode 100644 index 0000000..4bdaa13 --- /dev/null +++ b/.history/views/services_20251025215321.ejs @@ -0,0 +1,289 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ <%- __('services.why_choose.title') %> +

+ +
+ +
+
+ +
+
+

<%- __('services.why_choose.modern_tech.title') %>

+

+ <%- __('services.why_choose.modern_tech.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.expert_team.title') %>

+

+ <%- __('services.why_choose.expert_team.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.fast_response.title') %>

+

+ <%- __('services.why_choose.fast_response.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.continuous_support.title') %>

+

+ <%- __('services.why_choose.continuous_support.description') %> +

+
+
+
+
+ + +
+
+
+ +

<%- __('services.why_choose.quality_guarantee.title') %>

+

+ <%- __('services.why_choose.quality_guarantee.subtitle') %> +

+
+
+
+
+
+
+ + +
+ +
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251025215521.ejs b/.history/views/services_20251025215521.ejs new file mode 100644 index 0000000..4bdaa13 --- /dev/null +++ b/.history/views/services_20251025215521.ejs @@ -0,0 +1,289 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ <%- __('services.why_choose.title') %> +

+ +
+ +
+
+ +
+
+

<%- __('services.why_choose.modern_tech.title') %>

+

+ <%- __('services.why_choose.modern_tech.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.expert_team.title') %>

+

+ <%- __('services.why_choose.expert_team.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.fast_response.title') %>

+

+ <%- __('services.why_choose.fast_response.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.continuous_support.title') %>

+

+ <%- __('services.why_choose.continuous_support.description') %> +

+
+
+
+
+ + +
+
+
+ +

<%- __('services.why_choose.quality_guarantee.title') %>

+

+ <%- __('services.why_choose.quality_guarantee.subtitle') %> +

+
+
+
+
+
+
+ + +
+ +
+ + + + + \ No newline at end of file diff --git a/.history/views/services_20251026093148.ejs b/.history/views/services_20251026093148.ejs new file mode 100644 index 0000000..a69031a --- /dev/null +++ b/.history/views/services_20251026093148.ejs @@ -0,0 +1,286 @@ + + + + + +
+
+

+ <%- __('services.hero.title') %> <%- __('services.hero.title_highlight') %> +

+

+ <%- __('services.hero.subtitle') %> +

+
+
+ + +
+
+
+ <% if (services && services.length > 0) { %> + <% services.forEach((service, index) => { %> +
+ + +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + <%= service.category %> + +
+ + +

+ <%= service.name %> +

+ + +

+ <%= service.shortDescription || service.description %> +

+ + + <% if (service.pricing) { %> +
+
<%- __('services.cards.starting_price') %>
+
+ <%= service.pricing.basePrice ? service.pricing.basePrice.toLocaleString() : __('services.cards.consultation') %> + <% if (service.pricing.basePrice) { %> + 원~ + <% } %> +
+
+ <% } %> + + + + + + <% if (service.featured) { %> +
+ + <%- __('services.cards.popular') %> + +
+ <% } %> +
+
+
+
+ <% }) %> + <% } else { %> +
+ +

<%- __('services.cards.coming_soon') %>

+

<%- __('services.cards.coming_soon_desc') %>

+
+ <% } %> +
+
+
+ + +
+
+
+

+ <%- __('services.process.title') %> +

+

+ <%- __('services.process.subtitle') %> +

+
+ +
+ +
+
+ 1 +
+

<%- __('services.process.step1.title') %>

+

+ <%- __('services.process.step1.description') %> +

+
+ + +
+
+ 2 +
+

<%- __('services.process.step2.title') %>

+

+ <%- __('services.process.step2.description') %> +

+
+ + +
+
+ 3 +
+

<%- __('services.process.step3.title') %>

+

+ <%- __('services.process.step3.description') %> +

+
+ + +
+
+ 4 +
+

<%- __('services.process.step4.title') %>

+

+ <%- __('services.process.step4.description') %> +

+
+
+
+
+ + +
+
+
+ +
+

+ <%- __('services.why_choose.title') %> +

+ +
+ +
+
+ +
+
+

<%- __('services.why_choose.modern_tech.title') %>

+

+ <%- __('services.why_choose.modern_tech.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.expert_team.title') %>

+

+ <%- __('services.why_choose.expert_team.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.fast_response.title') %>

+

+ <%- __('services.why_choose.fast_response.description') %> +

+
+
+ + +
+
+ +
+
+

<%- __('services.why_choose.continuous_support.title') %>

+

+ <%- __('services.why_choose.continuous_support.description') %> +

+
+
+
+
+ + +
+
+
+ +

<%- __('services.why_choose.quality_guarantee.title') %>

+

+ <%- __('services.why_choose.quality_guarantee.subtitle') %> +

+
+
+
+
+
+
+ + +
+
+

+ <%- __('services.cta.title') %> +

+

+ <%- __('services.cta.subtitle') %> +

+ + + <%- __('services.cta.calculate_cost') %> + + + <%- __('services.cta.view_portfolio') %> + +
+

+
+ + + + + \ No newline at end of file diff --git a/cookies.txt b/cookies.txt deleted file mode 100644 index c800e2f..0000000 --- a/cookies.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Netscape HTTP Cookie File -# https://curl.se/docs/http-cookies.html -# This file was generated by libcurl! Edit at your own risk. - -#HttpOnly_localhost FALSE / FALSE 1761640548 connect.sid s%3Avv-twFS8-lM9AQEsCor7mjMMTS7bSAIi.D1LM3WdMrHiBcGv175fp8L3ORi4gsGQ3RSm9a3H59to diff --git a/locales/en.json b/locales/en.json index 538c0a2..a807050 100644 --- a/locales/en.json +++ b/locales/en.json @@ -70,10 +70,52 @@ "process": { "title": "Project Implementation Process", "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { + "step1": { "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" + "description": "Accurately understand customer requirements and plan optimal solutions" + }, + "step2": { + "title": "Design and Architecture", + "description": "Design user-centered intuitive design and robust system architecture" + }, + "step3": { + "title": "Development and Implementation", + "description": "Develop efficient and scalable solutions using the latest technology and best practices" + }, + "step4": { + "title": "Testing and Deployment", + "description": "Ensure quality through thorough testing and proceed with stable deployment" } + }, + "why_choose": { + "title": "Why Choose SmartSolTech?", + "modern_tech": { + "title": "Modern Technology Utilization", + "description": "Always keep up with the latest technology trends and use proven technology stacks to provide future-oriented solutions." + }, + "expert_team": { + "title": "Expert Team", + "description": "A team of experts in each field collaborates to guarantee the highest quality results." + }, + "fast_response": { + "title": "Fast Response", + "description": "Complete projects within scheduled timeframes through quick communication and efficient project management." + }, + "continuous_support": { + "title": "Continuous Support", + "description": "Maintain long-term partnerships through continuous maintenance and technical support even after project completion." + }, + "quality_guarantee": { + "title": "Quality Guarantee", + "subtitle": "For customer satisfaction\nHighest quality service" + } + }, + "cta": { + "title": "Ready to Start Your Project?", + "subtitle": "We'll propose the optimal solution through free consultation", + "free_consultation": "Apply for Free Consultation", + "calculate_cost": "Calculate Cost", + "view_portfolio": "View Portfolio" } }, "portfolio": { @@ -103,9 +145,46 @@ "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" } }, + "portfolio_page": { + "title": "Our Portfolio", + "subtitle": "Discover innovative projects and creative solutions", + "categories": { + "all": "All", + "web-development": "Web Development", + "mobile-app": "Mobile App", + "ui-ux-design": "UI/UX Design", + "branding": "Branding", + "marketing": "Digital Marketing" + }, + "buttons": { + "details": "View Details", + "projectDetails": "Project Details", + "loadMore": "Load More Projects", + "contact": "Request Project", + "calculate": "Calculate Cost" + }, + "empty": { + "title": "No portfolio yet", + "subtitle": "We'll be showcasing amazing projects soon!" + }, + "cta": { + "title": "Be the star of the next project", + "subtitle": "Create innovative digital solutions with us" + }, + "labels": { + "featured": "FEATURED", + "views": "views", + "likes": "likes" + } + }, "calculator": { "title": "Project Cost Calculator", "subtitle": "Select your desired services and requirements to get accurate cost estimates in real time", + "next_step": "Next Step", + "prev_step": "Previous Step", + "calculate": "Calculate", + + "reset": "Reset", "meta": { "title": "Project Cost Calculator", "description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator" @@ -114,6 +193,48 @@ "title": "Check Your Project Estimate", "subtitle": "Select your desired services and requirements to calculate costs in real time", "button": "Use Cost Calculator" + }, + "step1": { + "title": "Choose Your Services", + "nav_title": "Step 1: Services", + "subtitle": "Select the services you need for your project" + }, + "step2": { + "title": "Project Details", + "nav_title": "Step 2: Details", + "subtitle": "Tell us about your project complexity and timeline" + }, + "complexity": { + "title": "Project Complexity", + "simple": "Simple", + "simple_desc": "Basic features and standard functionality", + "medium": "Medium", + "medium_desc": "Custom features and integrations", + "complex": "Complex", + "complex_desc": "Advanced features and complex integrations" + }, + "timeline": { + "title": "Project Timeline", + "standard": "Standard (4-8 weeks)", + "standard_desc": "Normal development timeline", + "rush": "Rush (2-4 weeks)", + "rush_desc": "Expedited delivery with additional cost", + "extended": "Extended (8+ weeks)", + "extended_desc": "Flexible timeline with cost optimization" + }, + "result": { + "title": "Project Estimate", + "nav_title": "Result", + "subtitle": "Your estimated project cost", + "estimated_price": "Estimated Price", + "price_note": "This is an approximate cost. Final price may vary based on project complexity.", + "summary": "Project Summary", + "get_quote": "Get Detailed Quote", + "contact_note": "Contact us for a detailed consultation and precise quote.", + "recalculate": "Calculate Again", + "selected_services": "Selected Services", + "complexity": "Complexity", + "timeline": "Timeline" } }, "contact": { @@ -221,6 +342,9 @@ }, "footer": { "description": "Digital solution specialist leading innovation", + "company": { + "description": "Digital solution specialist leading innovation" + }, "links": { "title": "Quick Links" }, @@ -230,7 +354,9 @@ "phone": "+82-2-1234-5678", "address": "123 Teheran-ro, Gangnam-gu, Seoul" }, - "copyright": "© 2024 SmartSolTech. All rights reserved." + "copyright": "© {{year}} SmartSolTech. All rights reserved.", + "privacy": "Privacy Policy", + "terms": "Terms of Service" }, "theme": { "light": "Light Theme", @@ -295,37 +421,5 @@ "portfolio": "Portfolio", "contact": "Contact", "calculator": "Calculator" - }, - "portfolio_page": { - "title": "Our Portfolio", - "subtitle": "Discover innovative projects and creative solutions", - "categories": { - "all": "All", - "web-development": "Web Development", - "mobile-app": "Mobile App", - "ui-ux-design": "UI/UX Design", - "branding": "Branding", - "marketing": "Digital Marketing" - }, - "buttons": { - "details": "View Details", - "projectDetails": "Project Details", - "loadMore": "Load More Projects", - "contact": "Request Project", - "calculate": "Calculate Cost" - }, - "empty": { - "title": "No portfolio yet", - "subtitle": "We'll be showcasing amazing projects soon!" - }, - "cta": { - "title": "Be the star of the next project", - "subtitle": "Create innovative digital solutions with us" - }, - "labels": { - "featured": "FEATURED", - "views": "views", - "likes": "likes" - } } } \ No newline at end of file diff --git a/locales/kk.json b/locales/kk.json index e868476..0660234 100644 --- a/locales/kk.json +++ b/locales/kk.json @@ -6,381 +6,356 @@ "portfolio": "Портфолио", "contact": "Байланыс", "calculator": "Калькулятор", - "admin": "Админ", - "home - SmartSolTech": "navigation.home - SmartSolTech" + "admin": "Әкімші" }, "hero": { "title": { - "smart": "hero.title.smart", - "solutions": "hero.title.solutions" + "smart": "Ақылды", + "solutions": "Шешімдер" }, - "subtitle": "Шешімдер", - "description": "Инновациялық веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн арқылы бизнестің цифрлық трансформациясын жүргіземіз", + "subtitle": "Инновациялық технологиялармен бизнесіңізді дамытыңыз", + "description": "Бизнесіңіздің цифрлық трансформациясы үшін инновациялық веб-дамыту, мобильді қосымшалар, UI/UX дизайн", "cta": { - "start": "hero.cta.start", - "portfolio": "hero.cta.portfolio" - }, - "cta_primary": "Жобаны бастау", - "cta_secondary": "Портфолионы көру" + "start": "Бастау", + "portfolio": "Портфолионы көру" + } }, "services": { "title": { - "our": "services.title.our", - "services": "services.title.services" + "our": "Біздің", + "services": "Қызметтер" }, - "subtitle": "services.subtitle", - "description": "Заманауи технология және шығармашылық идеялармен жасалған цифрлық шешімдер", + "subtitle": "Идеяларыңызды шындыққа айналдыру үшін кәсіби дамыту қызметтері", + "description": "Озық технологиялар мен шығармашылық идеяларды пайдаланған цифрлық шешімдер", "view_all": "Барлық қызметтерді көру", "web": { - "title": "services.web.title", - "description": "services.web.description", - "price": "services.web.price" + "title": "Веб-дамыту", + "description": "Бейімделетін веб-сайттар мен веб-қосымшалар", + "price": "$500-дан бастап" }, "mobile": { - "title": "services.mobile.title", - "description": "services.mobile.description", - "price": "services.mobile.price" + "title": "Мобильді қосымшалар", + "description": "iOS және Android үшін жергілікті қосымшалар дамыту", + "price": "$1,000-нан бастап" }, "design": { - "title": "services.design.title", - "description": "services.design.description", - "price": "services.design.price" + "title": "UI/UX Дизайн", + "description": "Интерфейс пен пайдаланушы тәжірибесінің дизайны", + "price": "$300-дан бастап" }, "marketing": { - "title": "services.marketing.title", - "description": "services.marketing.description", - "price": "services.marketing.price" + "title": "Цифрлық маркетинг", + "description": "SEO, SMM, жарнама басқаруы", + "price": "$200-ден бастап" }, "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" + "title": "Қызметтер", + "description": "SmartSolTech-тің кәсіби қызметтерін танысыңыз. Веб-дамыту, мобильді қосымшалар, UI/UX дизайн, цифрлық маркетинг және басқа технологиялық шешімдер.", + "keywords": "веб-дамыту, мобильді қосымшалар, UI/UX дизайн, цифрлық маркетинг, технологиялық шешімдер, SmartSolTech" }, "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" + "title": "Біздің", + "title_highlight": "Қызметтер", + "subtitle": "Инновациялық технологиялармен бизнес өсуін қолдау" }, "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" + "starting_price": "Бастапқы баға", + "consultation": "кеңес беру", + "contact": "Байланысу", + "calculate_cost": "Құнын есептеу", + "popular": "Танымал", + "coming_soon": "Қызметтер жақында пайда болады", + "coming_soon_desc": "Жақында біз әртүрлі қызметтерді ұсынамыз!" }, "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements and" + "title": "Жоба іске асыру процесі", + "subtitle": "Біз жобаларды жүйелі және кәсіби тәсілмен жүргіземіз", + "step1": { + "title": "Кеңес беру және жоспарлау", + "description": "Клиенттің талаптарын дәл түсініп, оңтайлы шешімді жоспарлаймыз" + }, + "step2": { + "title": "Дизайн және жобалау", + "description": "Пайдаланушыға бағытталған интуитивті дизайн мен сенімді жүйе архитектурасын жобалаймыз" + }, + "step3": { + "title": "Дамыту және іске асыру", + "description": "Ең жаңа технологиялар мен ең жақсы тәжірибелерді пайдаланып тиімді және кеңейтілетін шешімдер дамытамыз" + }, + "step4": { + "title": "Тестілеу және енгізу", + "description": "Мұқият тестілеу арқылы сапаны қамтамасыз етіп, тұрақты енгізуді жүргіземіз" } }, - "title_highlight": "Қызметтер", - "web_development": { - "title": "Веб-әзірлеу", - "description": "Заманауи және бейімделгіш веб-сайттар мен веб-қосымшаларды әзірлеу", - "price": "$5,000~" + "why_choose": { + "title": "Неліктен SmartSolTech таңдау керек?", + "modern_tech": { + "title": "Заманауи технологияларды пайдалану", + "description": "Үнемі ең жаңа технологиялық трендтерді қадағалап, дәлелденген технологиялық стекті пайдаланып болашаққа бағытталған шешімдер ұсынамыз." + }, + "expert_team": { + "title": "Сарапшылар командасы", + "description": "Әр саладағы сарапшылардан тұратын команда ынтымақтасып ең жоғары сапалы нәтижелерді қамтамасыз етеді." + }, + "fast_response": { + "title": "Жылдам жауап", + "description": "Жылдам коммуникация және тиімді жоба басқаруы арқылы белгіленген мерзімде жобаларды аяқтаймыз." + }, + "continuous_support": { + "title": "Үздіксіз қолдау", + "description": "Жоба аяқталғаннан кейін де үздіксіз қызмет көрсету және техникалық қолдау арқылы ұзақ мерзімді серіктестікті сақтаймыз." + }, + "quality_guarantee": { + "title": "Сапа кепілдігі", + "subtitle": "Клиенттердің қанағаттануы үшін\nЕң жоғары сапалы қызмет" + } }, - "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~" + "cta": { + "title": "Жобаңызды бастауға дайынсыз ба?", + "subtitle": "Тегін кеңес беру арқылы оңтайлы шешімді ұсынамыз", + "free_consultation": "Тегін кеңес беруге өтініш беру", + "calculate_cost": "Құнын есептеу", + "view_portfolio": "Портфолионы көру" } }, "portfolio": { "title": { - "recent": "portfolio.title.recent", - "projects": "portfolio.title.projects", - "our": "Our", - "portfolio": "Portfolio" + "recent": "Соңғы", + "projects": "Жобалар" }, - "subtitle": "portfolio.subtitle", - "description": "Тұтынушылардың табысы үшін аяқталған жобаларды тексеріңіз", - "view_details": "Толығырақ", + "subtitle": "Сәтті аяқталған жобалармен танысыңыз", + "description": "Клиенттердің табысы үшін орындалған жобаларды көріңіз", + "view_details": "Толық ақпарат", "view_all": "Барлық портфолионы көру", "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" + "all": "Барлығы", + "web": "Веб-дамыту", + "mobile": "Мобильді қосымшалар", + "uiux": "UI/UX Дизайн" }, - "project_details": "Project Details", + "project_details": "Жоба егжей-тегжейлері", "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" + "ecommerce": "Электрондық сауда", + "title": "Электрондық сауда платформасы", + "description": "Интуитивті интерфейсі бар заманауи интернет-сауда шешімі" }, "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", - "og_title": "Portfolio - SmartSolTech", - "og_description": "SmartSolTech's diverse projects and success stories" - }, - "title_highlight": "Жобалар", - "view_project": "View Project" + "title": "Портфолио", + "description": "SmartSolTech-тің әртүрлі жобалары мен табыс тарихтарын танысыңыз. Веб-дамыту, мобильді қосымшалар, UI/UX дизайн портфолиосы.", + "keywords": "портфолио, веб-дамыту, мобильді қосымшалар, UI/UX дизайн, жобалар, SmartSolTech" + } }, - "calculator": { - "title": "Жоба Құнының Калькуляторы", - "subtitle": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта дәл бағаны алыңыз", - "meta": { - "title": "Жоба құнының калькуляторы", - "description": "Веб-әзірлеу, мобильді қосымша немесе дизайн жобасының құнын біздің интерактивті калькулятормен есептеңіз" + "portfolio_page": { + "title": "Біздің портфолио", + "subtitle": "Инновациялық жобалар мен шығармашылық шешімдерді ашыңыз", + "categories": { + "all": "Барлығы", + "web-development": "Веб-дамыту", + "mobile-app": "Мобильді қосымшалар", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифрлық маркетинг" + }, + "buttons": { + "details": "Толық ақпарат", + "projectDetails": "Жоба егжей-тегжейлері", + "loadMore": "Көбірек жобаларды жүктеу", + "contact": "Жоба тапсырысу", + "calculate": "Құнын есептеу" + }, + "empty": { + "title": "Портфолио әзірше бос", + "subtitle": "Жақында біз керемет жобаларды таныстырамыз!" }, "cta": { - "title": "Жобаның бағасын тексеріңіз", - "subtitle": "Қажетті қызметтер мен талаптарды таңдап, нақты уақытта бағаны есептейміз", + "title": "Келесі жобаның жұлдызы болыңыз", + "subtitle": "Бізбен бірге инновациялық цифрлық шешімдер жасаңыз" + }, + "labels": { + "featured": "ҰСЫНЫЛҒАН", + "views": "көрініс", + "likes": "ұнату" + } + }, + "calculator": { + "title": "Жоба құнының калькуляторы", + "subtitle": "Нақты уақытта дәл бағалау алу үшін қажетті қызметтер мен талаптарды таңдаңыз", + "next_step": "Келесі қадам", + "prev_step": "Алдыңғы қадам", + "calculate": "Есептеу", + "reset": "Қайта бастау", + "meta": { + "title": "Жоба құнының калькуляторы", + "description": "Біздің интерактивті калькулятормен веб-дамыту, мобильді қосымша немесе дизайн жобаңыздың құнын есептеңіз" + }, + "cta": { + "title": "Жобаңыздың бағасын тексеріңіз", + "subtitle": "Нақты уақытта құнды есептеу үшін қажетті қызметтер мен талаптарды таңдаңыз", "button": "Құн калькуляторын пайдалану" }, "step1": { - "title": "1-қадам: Қызмет таңдау", - "subtitle": "Қажетті қызметтерді таңдаңыз (бірнеше таңдауға болады)" + "title": "Қызметтеріңізді таңдаңыз", + "nav_title": "1-қадам: Қызметтер", + "subtitle": "Жобаңыз үшін қажетті қызметтерді таңдаңыз" }, "step2": { - "title": "2-қадам: Жоба мәліметтері", - "subtitle": "Жобаның күрделілігі мен мерзімін таңдаңыз" + "title": "Жоба егжей-тегжейлері", + "nav_title": "2-қадам: Егжей-тегжейлер", + "subtitle": "Жобаңыздың күрделілігі мен мерзімдері туралы айтыңыз" }, "complexity": { - "title": "Жобаның күрделілігі", + "title": "Жоба күрделілігі", "simple": "Қарапайым", - "simple_desc": "Негізгі функциялар, стандартты дизайн", + "simple_desc": "Негізгі функциялар мен стандартты функционал", "medium": "Орташа", - "medium_desc": "Қосымша функциялар, жеке дизайн", + "medium_desc": "Пайдаланушы функциялары мен интеграциялар", "complex": "Күрделі", - "complex_desc": "Кеңейтілген функциялар, күрделі интеграциялар" + "complex_desc": "Қосымша функциялар мен күрделі интеграциялар" }, "timeline": { - "title": "Әзірлеу мерзімі", - "standard": "Стандартты", - "standard_desc": "Қалыпты әзірлеу мерзімі", - "rush": "Асығыс", - "rush_desc": "Жылдам әзірлеу (+50%)", - "extended": "Кеңейтілген", - "extended_desc": "Икемді әзірлеу мерзімі (-20%)" + "title": "Жоба мерзімдері", + "standard": "Стандартты (4-8 апта)", + "standard_desc": "Қалыпты дамыту мерзімдері", + "rush": "Шұғыл (2-4 апта)", + "rush_desc": "Қосымша құнмен жылдамдатылған жеткізу", + "extended": "Кеңейтілген (8+ апта)", + "extended_desc": "Құн оңтайландырумен икемді мерзімдер" }, "result": { - "title": "Есептеу нәтижесі", - "subtitle": "Міне, сіздің алдын ала жоба құнының бағасы", - "estimated_price": "Алдын ала баға", - "price_note": "* Соңғы құн жоба мәліметтеріне байланысты өзгеруі мүмкін", + "title": "Жоба бағасы", + "subtitle": "Сіздің есептелген жоба құны", + "nav_title": "Баға", + "estimated_price": "Есептелген баға", + "price_note": "Бұл шамамен құн. Соңғы баға жобаның күрделілігіне байланысты өзгеруі мүмкін.", "summary": "Жоба қорытындысы", + "get_quote": "Толық ұсыныс алу", + "contact_note": "Толық кеңес беру және дәл ұсыныс алу үшін бізбен байланысыңыз.", + "recalculate": "Қайта есептеу", "selected_services": "Таңдалған қызметтер", "complexity": "Күрделілік", - "timeline": "Мерзім", - "get_quote": "Дәл ұсыныс алу", - "recalculate": "Қайта есептеу", - "contact_note": "Дәл ұсыныс алу және жоба мәліметтерін талқылау үшін бізбен байланысыңыз" - }, - "next_step": "Келесі қадам", - "prev_step": "Артқа", - "calculate": "Есептеу" + "timeline": "Мерзімдер" + } }, "contact": { "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" + "title": "Бізбен байланысыңыз", + "subtitle": "Біз сіздің идеяларыңызды өмірге келтіруге көмектесуге дайынбыз" }, "ready_title": "Жобаңызды бастауға дайынсыз ба?", "ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.", "form": { - "title": "contact.form.title", + "title": "Жоба сұранысы", "name": "Аты", "email": "Электрондық пошта", "phone": "Телефон", - "message": "Жобаңыз туралы қысқаша сипаттаңыз", - "submit": "Кеңес беру үшін өтініш беру", - "success": "contact.form.success", - "error": "contact.form.error", + "message": "Хабарлама", + "submit": "Сұраныс жіберу", + "success": "Сұраныс сәтті жіберілді", + "error": "Сұранысты жіберу кезінде қате пайда болды", "service": { - "title": "Service Interest", - "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" - }, - "service_interest": "Қызығатын қызмет", - "service_options": { - "select": "Қызығатын қызметті таңдаңыз", - "web_development": "Веб-әзірлеу", - "mobile_app": "Мобильді қосымша", - "ui_ux_design": "UI/UX дизайн", + "title": "Қызығушылық қызметі", + "select": "Қызығушылық қызметін таңдаңыз", + "web": "Веб-дамыту", + "mobile": "Мобильді қосымша", + "design": "UI/UX Дизайн", "branding": "Брендинг", "consulting": "Кеңес беру", "other": "Басқа" } }, "info": { - "title": "Contact Information" + "title": "Байланыс ақпараты" }, "phone": { - "title": "contact.phone.title", - "number": "contact.phone.number", - "hours": "Mon-Fri 9:00-18:00" + "title": "Телефон арқылы сұраныс", + "number": "+82-2-1234-5678", + "hours": "Дс-Жм 9:00-18:00" }, "email": { - "title": "contact.email.title", - "address": "contact.email.address", - "response": "Response within 24 hours" + "title": "Электрондық пошта арқылы сұраныс", + "address": "info@smartsoltech.co.kr", + "response": "24 сағат ішінде жауап" }, "telegram": { - "title": "contact.telegram.title", - "subtitle": "contact.telegram.subtitle" + "title": "Telegram", + "subtitle": "Жылдам жауап алу үшін" }, "address": { - "title": "Office Address", - "line1": "123 Teheran-ro, Gangnam-gu", - "line2": "Seoul, South Korea" + "title": "Кеңсе мекенжайы", + "line1": "Тегеран-ро көшесі, 123, Каннам-гу", + "line2": "Сеул, Оңтүстік Корея" }, "cta": { - "ready": "contact.cta.ready", - "start": "contact.cta.start", - "question": "contact.cta.question", - "subtitle": "contact.cta.subtitle" + "ready": "Дайынсыз ба?", + "start": "Бастау", + "question": "Сұрақтарыңыз бар ма?", + "subtitle": "Біз жобалар бойынша кеңес береміз" }, "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - }, - "phone_consultation": "Телефон кеңесі", - "email_inquiry": "Электрондық пошта сұрауы", - "telegram_chat": "Telegram чаты", - "instant_response": "Лезде жауап беру мүмкін", - "free_consultation": "Тегін кеңес беру өтініші" + "title": "Байланыс", + "description": "Жоба сұранысы немесе кеңес алу үшін кез келген уақытта бізбен байланысыңыз" + } }, "about": { "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" + "title": "SmartSolTech туралы", + "subtitle": "Инновациялар мен технологиялармен болашақты жасаймыз" }, "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." + "title": "Компания туралы ақпарат", + "description1": "SmartSolTech - 2020 жылы құрылған технологиялық компания, веб-дамыту, мобильді қосымшалар дамыту және UI/UX дизайн саласындағы сараптамасымен танылған.", + "description2": "Біз клиенттердің қажеттіліктерін дәл түсінеміз және ең жаңа технологияларды пайдаланып инновациялық шешімдер ұсынамыз." }, "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" + "projects": "Аяқталған жобалар", + "experience": "Жыл тәжірибе", + "clients": "Қанағаттанған клиенттер" }, "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." + "title": "Біздің миссия", + "description": "Біздің миссиямыз - технологиялар арқылы клиенттердің бизнес өсуін қолдау және цифрлық инновацияларға көшбасшылық ету." }, "values": { "innovation": { - "title": "Инновация", - "description": "Үздіксіз зерттеу-әзірлеу және заманауи технологияларды енгізу арқылы инновациялық шешімдерді ұсынамыз." + "title": "Инновациялар", + "description": "Біз үздіксіз зерттеулер мен озық технологияларды енгізу арқылы инновациялық шешімдер ұсынамыз." }, "quality": { "title": "Сапа", - "description": "Жоғары сапа стандарттарын сақтап, тұтынушылар қанағаттана алатын жоғары сапалы өнімдерді ұсынамыз." + "description": "Біз жоғары сапа стандарттарын ұстанамыз және клиенттер қанағаттанатын жоғары сапалы өнімдер ұсынамыз." }, "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." - }, - "title": "Негізгі", - "title_highlight": "Құндылықтар", - "description": "SmartSolTech ұстанатын негізгі құндылықтар", - "collaboration": { - "title": "Ынтымақтастық", - "description": "Тұтынушылармен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз." - }, - "growth": { - "title": "Өсу", - "description": "Тұтынушылармен бірге өсіп, үздіксіз оқу мен дамуды мақсат етеміз." + "title": "Серіктестік", + "description": "Біз клиенттермен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз." } }, "cta": { - "title": "Бірге табысқа жететін серіктес болыңыз", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us", - "description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз", - "partnership": "Серіктестік сұрауы", - "portfolio": "Портфолионы көру" + "title": "Біз бірге өсеміз", + "subtitle": "Идеяларыңызды шындыққа айналдырыңыз", + "button": "Бізбен байланысу" }, "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - }, - "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": "Қазақстанды білдіретін жаһандық цифрлық шешімдер компаниясы ретінде өсіп, бүкіл әлемдегі тұтынушылардың цифрлық инновацияларын басқару" - }, - "team": { - "title": "Біздің", - "title_highlight": "Команда", - "description": "Сараптама мен құлшынысты SmartSolTech командасын таныстырамыз" - }, - "tech_stack": { - "title": "Технологиялық", - "title_highlight": "Стек", - "description": "Заманауи технология мен дәлелденген құралдармен ең жақсы шешімдерді ұсынамыз", - "frontend": "Frontend", - "backend": "Backend", - "mobile": "Мобильді" + "title": "Біз туралы", + "description": "SmartSolTech - инновациялық технологиялармен клиенттердің бизнес өсуін қолдайтын кәсіби дамыту компаниясы" } }, "footer": { - "description": "Инновацияны басқаратын цифрлық шешімдер маманы", + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы", + "company": { + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы" + }, "links": { - "title": "footer.links.title", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "sitemap": "Sitemap" + "title": "Жылдам сілтемелер" }, "contact": { - "title": "footer.contact.title", - "email": "footer.contact.email", - "phone": "footer.contact.phone", - "address": "footer.contact.address" + "title": "Байланыс", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "Тегеран-ро көшесі, 123, Каннам-гу, Сеул" }, - "copyright": "footer.copyright", - "company": { - "description": "footer.company.description" - }, - "quick_links": "Жылдам сілтемелер", - "services": "Қызметтер", - "contact_info": "Байланыс ақпараты", - "follow_us": "Бізді іздеңіз", - "rights": "Барлық құқықтар сақталған.", - "privacy": "footer.privacy", - "terms": "footer.terms", - "social": { - "follow": "Follow Us" - } + "copyright": "© {{year}} SmartSolTech. Барлық құқықтар қорғалған.", + "privacy": "Құпиялылық саясаты", + "terms": "Пайдалану шарттары" }, "theme": { "light": "Ашық тема", @@ -391,135 +366,59 @@ "english": "English", "korean": "한국어", "russian": "Русский", - "kazakh": "Қазақша", - "ko": "language.ko" + "kazakh": "Қазақша" }, "common": { "loading": "Жүктелуде...", - "error": "Қате орын алды", + "error": "Қате пайда болды", "success": "Сәтті", "view_more": "Көбірек көру", "back": "Артқа", "next": "Келесі", "previous": "Алдыңғы", - "view_details": "common.view_details" + "view_details": "Толық ақпарат" }, "meta": { - "description": "meta.description", - "keywords": "meta.keywords", - "title": "meta.title" + "description": "SmartSolTech - Инновациялық веб-дамыту, мобильді қосымшалар дамыту, UI/UX дизайн", + "keywords": "веб-дамыту, мобильді қосымшалар, UI/UX дизайн, Корея", + "title": "SmartSolTech" }, "nav": { - "home": "nav.home", - "about": "nav.about", - "services": "nav.services", - "portfolio": "nav.portfolio", - "calculator": "nav.calculator" + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "calculator": "Калькулятор" }, "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin", - "login_title": "Admin Panel Login", - "login_subtitle": "Login to your account to manage the site", - "login_button": "Login", - "email": "Email", - "password": "Password", - "email_placeholder": "admin@smartsoltech.com", - "password_placeholder": "Enter password", - "back_to_site": "Back to site", - "dashboard_subtitle": "Overview of main site metrics", - "portfolio": "Portfolio", - "services": "Services", - "contacts": "Messages", - "settings": "Settings", - "users": "Users", - "logout": "Logout", - "view_site": "View site", - "view_all": "View all", - "portfolio_projects": "Projects", - "contact_messages": "Messages", - "recent_portfolio": "Recent projects", - "recent_contacts": "Recent messages", - "no_recent_portfolio": "No recent projects", - "no_recent_contacts": "No recent messages", - "quick_actions": "Quick actions", - "add_portfolio": "Add project", - "add_service": "Add service", - "site_settings": "Site settings", - "banner_editor": "Banner Editor", - "current_banner": "Current banner", - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio" - } + "login": "Әкімші панеліне кіру", + "dashboard": "Басқару панелі", + "title": "SmartSolTech Әкімші" }, "company": { "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", + "description": "Инновацияларға көшбасшылық ететін цифрлық шешімдер маманы", "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678", - "full_name": "SmartSolTech - Innovative Technology Solutions", - "tagline": "Future begins here", - "address": "Seoul, South Korea", - "social": { - "telegram": "@smartsoltech" - } + "phone": "+82-10-1234-5678" }, "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support", - "contact_us": "Contact us" + "page_not_found": "Бет табылмады", + "error_occurred": "Қате пайда болды", + "title": "Қате - SmartSolTech", + "default_title": "Қате пайда болды", + "default_message": "Сұранысты өңдеу кезінде мәселе туындады.", + "back_home": "Басты бетке оралу", + "go_back": "Артқа оралу", + "need_help": "Көмек керек пе?", + "help_message": "Егер мәселе қайталанса, кез келген уақытта бізбен байланысыңыз.", + "contact_support": "Қолдау қызметімен байланысу" }, "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "portfolio_page": { - "title": "Our Portfolio", - "subtitle": "Discover innovative projects and creative solutions", - "categories": { - "all": "All", - "web-development": "Web Development", - "mobile-app": "Mobile App", - "ui-ux-design": "UI/UX Design", - "branding": "Branding", - "marketing": "Digital Marketing" - }, - "buttons": { - "details": "View Details", - "projectDetails": "Project Details", - "loadMore": "Load More Projects", - "contact": "Request Project", - "calculate": "Calculate Cost" - }, - "empty": { - "title": "No portfolio yet", - "subtitle": "We'll be showcasing amazing projects soon!" - }, - "cta": { - "title": "Be the star of the next project", - "subtitle": "Create innovative digital solutions with us" - }, - "labels": { - "featured": "FEATURED", - "views": "views", - "likes": "likes" - } - }, - "undefined - SmartSolTech": "undefined - SmartSolTech" + "home": "Басты бет", + "about": "Біз туралы", + "services": "Қызметтер", + "portfolio": "Портфолио", + "contact": "Байланыс", + "calculator": "Калькулятор" + } } \ No newline at end of file diff --git a/.history/locales/kk_20251019181629.json b/locales/kk_backup.json similarity index 50% rename from .history/locales/kk_20251019181629.json rename to locales/kk_backup.json index 493faeb..e868476 100644 --- a/.history/locales/kk_20251019181629.json +++ b/locales/kk_backup.json @@ -2,23 +2,83 @@ "navigation": { "home": "Басты бет", "about": "Біз туралы", - "services": "Қызметтер", + "services": "Қызметтер", "portfolio": "Портфолио", "contact": "Байланыс", "calculator": "Калькулятор", - "admin": "Админ" + "admin": "Админ", + "home - SmartSolTech": "navigation.home - SmartSolTech" }, "hero": { - "title": "Ақылды Технологиялық", + "title": { + "smart": "hero.title.smart", + "solutions": "hero.title.solutions" + }, "subtitle": "Шешімдер", "description": "Инновациялық веб-әзірлеу, мобильді қосымшалар, UI/UX дизайн арқылы бизнестің цифрлық трансформациясын жүргіземіз", + "cta": { + "start": "hero.cta.start", + "portfolio": "hero.cta.portfolio" + }, "cta_primary": "Жобаны бастау", "cta_secondary": "Портфолионы көру" }, "services": { - "title": "Біздің", - "title_highlight": "Қызметтер", + "title": { + "our": "services.title.our", + "services": "services.title.services" + }, + "subtitle": "services.subtitle", "description": "Заманауи технология және шығармашылық идеялармен жасалған цифрлық шешімдер", + "view_all": "Барлық қызметтерді көру", + "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" + }, + "meta": { + "title": "Services", + "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", + "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" + }, + "hero": { + "title": "Our", + "title_highlight": "Services", + "subtitle": "Support business growth with innovative technology" + }, + "cards": { + "starting_price": "Starting Price", + "consultation": "consultation", + "contact": "Contact", + "calculate_cost": "Calculate Cost", + "popular": "Popular", + "coming_soon": "Services Coming Soon", + "coming_soon_desc": "We'll soon offer various services!" + }, + "process": { + "title": "Project Implementation Process", + "subtitle": "We conduct projects with systematic and professional processes", + "consultation": { + "title": "Consultation and Planning", + "description": "Accurately understand customer requirements and" + } + }, + "title_highlight": "Қызметтер", "web_development": { "title": "Веб-әзірлеу", "description": "Заманауи және бейімделгіш веб-сайттар мен веб-қосымшаларды әзірлеу", @@ -38,15 +98,40 @@ "title": "Цифрлық маркетинг", "description": "SEO, әлеуметтік медиа, онлайн жарнама арқылы цифрлық маркетинг", "price": "$2,000~" - }, - "view_all": "Барлық қызметтерді көру" + } }, "portfolio": { - "title": "Соңғы", - "title_highlight": "Жобалар", + "title": { + "recent": "portfolio.title.recent", + "projects": "portfolio.title.projects", + "our": "Our", + "portfolio": "Portfolio" + }, + "subtitle": "portfolio.subtitle", "description": "Тұтынушылардың табысы үшін аяқталған жобаларды тексеріңіз", "view_details": "Толығырақ", - "view_all": "Барлық портфолионы көру" + "view_all": "Барлық портфолионы көру", + "categories": { + "all": "All", + "web": "Web Development", + "mobile": "Mobile Apps", + "uiux": "UI/UX Design" + }, + "project_details": "Project Details", + "default": { + "ecommerce": "E-commerce", + "title": "E-commerce Platform", + "description": "Modern online commerce solution with intuitive interface" + }, + "meta": { + "title": "Portfolio", + "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", + "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech", + "og_title": "Portfolio - SmartSolTech", + "og_description": "SmartSolTech's diverse projects and success stories" + }, + "title_highlight": "Жобалар", + "view_project": "View Project" }, "calculator": { "title": "Жоба Құнының Калькуляторы", @@ -104,17 +189,31 @@ "calculate": "Есептеу" }, "contact": { + "hero": { + "title": "Contact Us", + "subtitle": "We're here to help bring your ideas to life" + }, "ready_title": "Жобаңызды бастауға дайынсыз ба?", "ready_description": "Идеяларыңызды шындыққа айналдырыңыз. Сарапшылар ең жақсы шешімдерді ұсынады.", - "phone_consultation": "Телефон кеңесі", - "email_inquiry": "Электрондық пошта сұрауы", - "telegram_chat": "Telegram чаты", - "instant_response": "Лезде жауап беру мүмкін", - "free_consultation": "Тегін кеңес беру өтініші", "form": { + "title": "contact.form.title", "name": "Аты", "email": "Электрондық пошта", "phone": "Телефон", + "message": "Жобаңыз туралы қысқаша сипаттаңыз", + "submit": "Кеңес беру үшін өтініш беру", + "success": "contact.form.success", + "error": "contact.form.error", + "service": { + "title": "Service Interest", + "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" + }, "service_interest": "Қызығатын қызмет", "service_options": { "select": "Қызығатын қызметті таңдаңыз", @@ -124,12 +223,102 @@ "branding": "Брендинг", "consulting": "Кеңес беру", "other": "Басқа" - }, - "message": "Жобаңыз туралы қысқаша сипаттаңыз", - "submit": "Кеңес беру үшін өтініш беру" - } + } + }, + "info": { + "title": "Contact Information" + }, + "phone": { + "title": "contact.phone.title", + "number": "contact.phone.number", + "hours": "Mon-Fri 9:00-18:00" + }, + "email": { + "title": "contact.email.title", + "address": "contact.email.address", + "response": "Response within 24 hours" + }, + "telegram": { + "title": "contact.telegram.title", + "subtitle": "contact.telegram.subtitle" + }, + "address": { + "title": "Office Address", + "line1": "123 Teheran-ro, Gangnam-gu", + "line2": "Seoul, South Korea" + }, + "cta": { + "ready": "contact.cta.ready", + "start": "contact.cta.start", + "question": "contact.cta.question", + "subtitle": "contact.cta.subtitle" + }, + "meta": { + "title": "Contact", + "description": "Contact us anytime for project inquiries or consultation" + }, + "phone_consultation": "Телефон кеңесі", + "email_inquiry": "Электрондық пошта сұрауы", + "telegram_chat": "Telegram чаты", + "instant_response": "Лезде жауап беру мүмкін", + "free_consultation": "Тегін кеңес беру өтініші" }, "about": { + "hero": { + "title": "About SmartSolTech", + "subtitle": "Creating the future with innovation and technology" + }, + "company": { + "title": "Company Information", + "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", + "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." + }, + "stats": { + "projects": "Completed Projects", + "experience": "Years Experience", + "clients": "Satisfied Customers" + }, + "mission": { + "title": "Our Mission", + "description": "Our mission is to support customer business growth through technology and lead digital innovation." + }, + "values": { + "innovation": { + "title": "Инновация", + "description": "Үздіксіз зерттеу-әзірлеу және заманауи технологияларды енгізу арқылы инновациялық шешімдерді ұсынамыз." + }, + "quality": { + "title": "Сапа", + "description": "Жоғары сапа стандарттарын сақтап, тұтынушылар қанағаттана алатын жоғары сапалы өнімдерді ұсынамыз." + }, + "partnership": { + "title": "Partnership", + "description": "We create the best results through close communication and collaboration with customers." + }, + "title": "Негізгі", + "title_highlight": "Құндылықтар", + "description": "SmartSolTech ұстанатын негізгі құндылықтар", + "collaboration": { + "title": "Ынтымақтастық", + "description": "Тұтынушылармен тығыз қарым-қатынас пен ынтымақтастық арқылы ең жақсы нәтижелерді жасаймыз." + }, + "growth": { + "title": "Өсу", + "description": "Тұтынушылармен бірге өсіп, үздіксіз оқу мен дамуды мақсат етеміз." + } + }, + "cta": { + "title": "Бірге табысқа жететін серіктес болыңыз", + "subtitle": "Turn your ideas into reality", + "button": "Contact Us", + "description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз", + "partnership": "Серіктестік сұрауы", + "portfolio": "Портфолионы көру" + }, + "meta": { + "title": "About Us", + "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" + }, "hero_title": "Туралы", "hero_highlight": "SmartSolTech", "hero_description": "Инновациялық технологиямен тұтынушылардың табысына жетелейтін цифрлық шешімдер маманы", @@ -140,40 +329,19 @@ "stats": { "projects": "100+", "projects_label": "Аяқталған жобалар", - "clients": "50+", + "clients": "50+", "clients_label": "Қанағаттанған тұтынушылар", "experience": "4 жыл", "experience_label": "Саладағы тәжірибе" }, "mission": "Біздің миссия", "mission_text": "Технология арқылы барлық бизнестердің цифрлық дәуірде табысқа жетуіне көмектесу", - "vision": "Біздің көзқарас", + "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": "Команда", + "title_highlight": "Команда", "description": "Сараптама мен құлшынысты SmartSolTech командасын таныстырамыз" }, "tech_stack": { @@ -181,35 +349,50 @@ "title_highlight": "Стек", "description": "Заманауи технология мен дәлелденген құралдармен ең жақсы шешімдерді ұсынамыз", "frontend": "Frontend", - "backend": "Backend", + "backend": "Backend", "mobile": "Мобильді" - }, - "cta": { - "title": "Бірге табысқа жететін серіктес болыңыз", - "description": "SmartSolTech-пен бизнесіңізді келесі деңгейге дамытыңыз", - "partnership": "Серіктестік сұрауы", - "portfolio": "Портфолионы көру" } }, "footer": { - "company": "SmartSolTech", "description": "Инновацияны басқаратын цифрлық шешімдер маманы", + "links": { + "title": "footer.links.title", + "privacy": "Privacy Policy", + "terms": "Terms of Service", + "sitemap": "Sitemap" + }, + "contact": { + "title": "footer.contact.title", + "email": "footer.contact.email", + "phone": "footer.contact.phone", + "address": "footer.contact.address" + }, + "copyright": "footer.copyright", + "company": { + "description": "footer.company.description" + }, "quick_links": "Жылдам сілтемелер", - "services": "Қызметтер", + "services": "Қызметтер", "contact_info": "Байланыс ақпараты", "follow_us": "Бізді іздеңіз", - "rights": "Барлық құқықтар сақталған." + "rights": "Барлық құқықтар сақталған.", + "privacy": "footer.privacy", + "terms": "footer.terms", + "social": { + "follow": "Follow Us" + } }, "theme": { "light": "Ашық тема", - "dark": "Қараңғы тема", + "dark": "Қараңғы тема", "toggle": "Теманы ауыстыру" }, "language": { "english": "English", "korean": "한국어", "russian": "Русский", - "kazakh": "Қазақша" + "kazakh": "Қазақша", + "ko": "language.ko" }, "common": { "loading": "Жүктелуде...", @@ -218,6 +401,125 @@ "view_more": "Көбірек көру", "back": "Артқа", "next": "Келесі", - "previous": "Алдыңғы" - } + "previous": "Алдыңғы", + "view_details": "common.view_details" + }, + "meta": { + "description": "meta.description", + "keywords": "meta.keywords", + "title": "meta.title" + }, + "nav": { + "home": "nav.home", + "about": "nav.about", + "services": "nav.services", + "portfolio": "nav.portfolio", + "calculator": "nav.calculator" + }, + "admin": { + "login": "Admin Panel Login", + "dashboard": "Dashboard", + "title": "SmartSolTech Admin", + "login_title": "Admin Panel Login", + "login_subtitle": "Login to your account to manage the site", + "login_button": "Login", + "email": "Email", + "password": "Password", + "email_placeholder": "admin@smartsoltech.com", + "password_placeholder": "Enter password", + "back_to_site": "Back to site", + "dashboard_subtitle": "Overview of main site metrics", + "portfolio": "Portfolio", + "services": "Services", + "contacts": "Messages", + "settings": "Settings", + "users": "Users", + "logout": "Logout", + "view_site": "View site", + "view_all": "View all", + "portfolio_projects": "Projects", + "contact_messages": "Messages", + "recent_portfolio": "Recent projects", + "recent_contacts": "Recent messages", + "no_recent_portfolio": "No recent projects", + "no_recent_contacts": "No recent messages", + "quick_actions": "Quick actions", + "add_portfolio": "Add project", + "add_service": "Add service", + "site_settings": "Site settings", + "banner_editor": "Banner Editor", + "current_banner": "Current banner", + "pages": { + "home": "Home page", + "about": "About us", + "services": "Services", + "portfolio": "Portfolio" + } + }, + "company": { + "name": "SmartSolTech", + "description": "Digital solution specialist leading innovation", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678", + "full_name": "SmartSolTech - Innovative Technology Solutions", + "tagline": "Future begins here", + "address": "Seoul, South Korea", + "social": { + "telegram": "@smartsoltech" + } + }, + "errors": { + "page_not_found": "Page not found", + "error_occurred": "Error occurred", + "title": "Error - SmartSolTech", + "default_title": "An Error Occurred", + "default_message": "A problem occurred while processing the request.", + "back_home": "Back to Home", + "go_back": "Go Back", + "need_help": "Need Help?", + "help_message": "If the problem persists, please contact us anytime.", + "contact_support": "Contact Support", + "contact_us": "Contact us" + }, + "pages": { + "home": "Home page", + "about": "About us", + "services": "Services", + "portfolio": "Portfolio", + "contact": "Contact", + "calculator": "Calculator" + }, + "portfolio_page": { + "title": "Our Portfolio", + "subtitle": "Discover innovative projects and creative solutions", + "categories": { + "all": "All", + "web-development": "Web Development", + "mobile-app": "Mobile App", + "ui-ux-design": "UI/UX Design", + "branding": "Branding", + "marketing": "Digital Marketing" + }, + "buttons": { + "details": "View Details", + "projectDetails": "Project Details", + "loadMore": "Load More Projects", + "contact": "Request Project", + "calculate": "Calculate Cost" + }, + "empty": { + "title": "No portfolio yet", + "subtitle": "We'll be showcasing amazing projects soon!" + }, + "cta": { + "title": "Be the star of the next project", + "subtitle": "Create innovative digital solutions with us" + }, + "labels": { + "featured": "FEATURED", + "views": "views", + "likes": "likes" + } + }, + "undefined - SmartSolTech": "undefined - SmartSolTech" } \ No newline at end of file diff --git a/locales/ko.json b/locales/ko.json index ea46c6e..d408b75 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1,301 +1,150 @@ { "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin" + "home": "홈", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "contact": "연락처", + "calculator": "비용계산기", + "admin": "관리자" }, "hero": { "title": { - "smart": "Smart", - "solutions": "Solutions" + "smart": "스마트", + "solutions": "솔루션" }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", + "subtitle": "혁신적인 기술로 비즈니스를 성장시키세요", + "description": "비즈니스의 디지털 전환을 이끄는 혁신적인 웹개발, 모바일 앱, UI/UX 디자인", "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" + "start": "시작하기", + "portfolio": "포트폴리오 보기" } }, "services": { "title": { - "our": "Our", - "services": "Services" + "our": "우리의", + "services": "서비스" }, - "subtitle": "Professional development services to turn your ideas into reality", - "description": "Digital solutions completed with cutting-edge technology and creative ideas", - "view_all": "View All Services", + "subtitle": "아이디어를 현실로 만드는 전문 개발 서비스", + "description": "최첨단 기술과 창의적 아이디어로 완성하는 디지털 솔루션", + "view_all": "모든 서비스 보기", "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" + "title": "웹 개발", + "description": "반응형 웹사이트 및 웹 애플리케이션 개발", + "price": "$500부터" }, "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" + "title": "모바일 앱", + "description": "iOS 및 Android 네이티브 앱 개발", + "price": "$1,000부터" }, "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" + "title": "UI/UX 디자인", + "description": "사용자 중심의 인터페이스 및 경험 디자인", + "price": "$300부터" }, "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" + "title": "디지털 마케팅", + "description": "SEO, 소셜미디어 마케팅, 광고 관리", + "price": "$200부터" }, "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" + "title": "서비스", + "description": "SmartSolTech의 전문 서비스를 확인해보세요. 웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅 및 기타 기술 솔루션.", + "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 디지털 마케팅, 기술 솔루션, SmartSolTech" }, "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" + "title": "우리의", + "title_highlight": "서비스", + "subtitle": "혁신적인 기술로 비즈니스 성장 지원" }, "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" + "starting_price": "시작 가격", + "consultation": "상담", + "contact": "연락하기", + "calculate_cost": "비용 계산", + "popular": "인기", + "coming_soon": "서비스 준비중", + "coming_soon_desc": "곧 다양한 서비스를 제공할 예정입니다!" }, "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" + "title": "프로젝트 수행 과정", + "subtitle": "체계적이고 전문적인 프로세스로 프로젝트를 진행합니다", + "step1": { + "title": "상담 및 기획", + "description": "고객의 요구사항을 정확히 파악하고 최적의 솔루션을 기획합니다" + }, + "step2": { + "title": "디자인 및 설계", + "description": "사용자 중심의 직관적인 디자인과 견고한 시스템 아키텍처를 설계합니다" + }, + "step3": { + "title": "개발 및 구현", + "description": "최신 기술과 모범 사례를 활용하여 효율적이고 확장 가능한 솔루션을 개발합니다" + }, + "step4": { + "title": "테스트 및 배포", + "description": "철저한 테스트를 통해 품질을 보장하고 안정적인 배포를 진행합니다" } + }, + "why_choose": { + "title": "왜 SmartSolTech를 선택해야 할까요?", + "modern_tech": { + "title": "최신 기술 활용", + "description": "항상 최신 기술 트렌드를 파악하고, 검증된 기술 스택을 활용하여 미래 지향적인 솔루션을 제공합니다." + }, + "expert_team": { + "title": "전문가 팀", + "description": "각 분야의 전문가들로 구성된 팀이 협력하여 최고 품질의 결과물을 보장합니다." + }, + "fast_response": { + "title": "빠른 대응", + "description": "신속한 커뮤니케이션과 효율적인 프로젝트 관리로 정해진 일정 내에 프로젝트를 완료합니다." + }, + "continuous_support": { + "title": "지속적인 지원", + "description": "프로젝트 완료 후에도 지속적인 유지보수와 기술 지원을 통해 장기적인 파트너십을 유지합니다." + }, + "quality_guarantee": { + "title": "품질 보장", + "subtitle": "고객 만족을 위한\n최고 품질의 서비스" + } + }, + "cta": { + "title": "프로젝트를 시작할 준비가 되셨나요?", + "subtitle": "무료 상담을 통해 최적의 솔루션을 제안해드리겠습니다", + "free_consultation": "무료 상담 신청", + "calculate_cost": "비용 계산하기", + "view_portfolio": "포트폴리오 보기" } }, "portfolio": { "title": { - "recent": "Recent", - "projects": "Projects" + "recent": "최근", + "projects": "프로젝트" }, - "subtitle": "Check out successfully completed projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", + "subtitle": "성공적으로 완료된 프로젝트들을 확인해보세요", + "description": "고객의 성공을 위해 완성한 프로젝트들을 살펴보세요", + "view_details": "자세히 보기", + "view_all": "모든 포트폴리오 보기", "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" + "all": "전체", + "web": "웹 개발", + "mobile": "모바일 앱", + "uiux": "UI/UX 디자인" }, - "project_details": "Project Details", + "project_details": "프로젝트 상세정보", "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" + "ecommerce": "이커머스", + "title": "이커머스 플랫폼", + "description": "직관적인 인터페이스를 가진 현대적인 온라인 커머스 솔루션" }, "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" + "title": "포트폴리오", + "description": "SmartSolTech의 다양한 프로젝트와 성공 사례들을 확인해보세요. 웹 개발, 모바일 앱, UI/UX 디자인 포트폴리오.", + "keywords": "포트폴리오, 웹 개발, 모바일 앱, UI/UX 디자인, 프로젝트, SmartSolTech" } }, - "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" - } - }, - "contact": { - "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" - }, - "ready_title": "Ready to Start Your Project?", - "ready_description": "Turn your ideas into reality. Experts provide the best solutions.", - "form": { - "title": "Project Inquiry", - "name": "Name", - "email": "Email", - "phone": "Phone", - "message": "Message", - "submit": "Send Inquiry", - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry", - "service": { - "title": "Service Interest", - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" - } - }, - "info": { - "title": "Contact Information" - }, - "phone": { - "title": "Phone Inquiry", - "number": "+82-2-1234-5678", - "hours": "Mon-Fri 9:00-18:00" - }, - "email": { - "title": "Email Inquiry", - "address": "info@smartsoltech.co.kr", - "response": "Response within 24 hours" - }, - "telegram": { - "title": "Telegram", - "subtitle": "For quick response" - }, - "address": { - "title": "Office Address", - "line1": "123 Teheran-ro, Gangnam-gu", - "line2": "Seoul, South Korea" - }, - "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" - }, - "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" - } - }, - "about": { - "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" - }, - "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." - }, - "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" - }, - "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." - }, - "values": { - "innovation": { - "title": "Innovation", - "description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology." - }, - "quality": { - "title": "Quality", - "description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with." - }, - "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." - } - }, - "cta": { - "title": "We'll Grow Together", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" - }, - "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" - } - }, - "footer": { - "description": "Digital solution specialist leading innovation", - "links": { - "title": "Quick Links" - }, - "contact": { - "title": "Contact", - "email": "info@smartsoltech.co.kr", - "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" - }, - "copyright": "© 2024 SmartSolTech. 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_details": "View Details" - }, - "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", - "title": "SmartSolTech" - }, - "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" - }, - "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin" - }, - "company": { - "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", - "email": "info@smartsoltech.kr", - "phone": "+82-10-1234-5678" - }, - "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" - }, - "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, "portfolio_page": { "title": "우리의 포트폴리오", "subtitle": "혁신적인 프로젝트와 창의적인 솔루션들을 만나보세요", @@ -327,5 +176,249 @@ "views": "조회수", "likes": "좋아요" } + }, + "calculator": { + "title": "프로젝트 비용 계산기", + "subtitle": "실시간으로 정확한 비용 추정을 받기 위해 원하는 서비스와 요구사항을 선택하세요", + "next_step": "다음 단계", + "prev_step": "이전 단계", + "calculate": "계산하기", + "reset": "초기화", + "meta": { + "title": "프로젝트 비용 계산기", + "description": "대화형 계산기로 웹 개발, 모바일 앱 또는 디자인 프로젝트의 비용을 계산하세요" + }, + "cta": { + "title": "프로젝트 견적을 확인해보세요", + "subtitle": "실시간 비용 계산을 위해 원하는 서비스와 요구사항을 선택하세요", + "button": "비용 계산기 사용하기" + }, + "step1": { + "title": "서비스를 선택하세요", + "nav_title": "단계 1: 서비스", + "subtitle": "프로젝트에 필요한 서비스를 선택하세요" + }, + "step2": { + "title": "프로젝트 세부사항", + "nav_title": "단계 2: 세부사항", + "subtitle": "프로젝트의 복잡도와 일정에 대해 알려주세요" + }, + "complexity": { + "title": "프로젝트 복잡도", + "simple": "간단함", + "simple_desc": "기본 기능 및 표준 기능", + "medium": "보통", + "medium_desc": "맞춤 기능 및 통합", + "complex": "복잡함", + "complex_desc": "고급 기능 및 복잡한 통합" + }, + "timeline": { + "title": "프로젝트 일정", + "standard": "표준 (4-8주)", + "standard_desc": "일반적인 개발 일정", + "rush": "급함 (2-4주)", + "rush_desc": "추가 비용으로 신속 배송", + "extended": "연장 (8주 이상)", + "extended_desc": "비용 최적화와 함께 유연한 일정" + }, + "result": { + "title": "프로젝트 견적", + "subtitle": "예상 프로젝트 비용", + "nav_title": "결과", + "estimated_price": "예상 가격", + "price_note": "이는 대략적인 비용입니다. 최종 가격은 프로젝트 복잡도에 따라 달라질 수 있습니다.", + "summary": "프로젝트 요약", + "get_quote": "상세 견적 받기", + "contact_note": "상세한 상담과 정확한 견적을 위해 저희에게 연락주세요.", + "recalculate": "다시 계산하기", + "selected_services": "선택된 서비스", + "complexity": "복잡도", + "timeline": "일정" + } + }, + "contact": { + "hero": { + "title": "연락하기", + "subtitle": "아이디어를 현실로 만들어드리겠습니다" + }, + "ready_title": "프로젝트를 시작할 준비가 되셨나요?", + "ready_description": "아이디어를 현실로 바꿔보세요. 전문가들이 최고의 솔루션을 제공합니다.", + "form": { + "title": "프로젝트 문의", + "name": "이름", + "email": "이메일", + "phone": "전화번호", + "message": "메시지", + "submit": "문의 보내기", + "success": "문의가 성공적으로 전송되었습니다", + "error": "문의 전송 중 오류가 발생했습니다", + "service": { + "title": "관심 서비스", + "select": "관심 있는 서비스를 선택하세요", + "web": "웹 개발", + "mobile": "모바일 앱", + "design": "UI/UX 디자인", + "branding": "브랜딩", + "consulting": "컨설팅", + "other": "기타" + } + }, + "info": { + "title": "연락처 정보" + }, + "phone": { + "title": "전화 문의", + "number": "+82-2-1234-5678", + "hours": "월-금 9:00-18:00" + }, + "email": { + "title": "이메일 문의", + "address": "info@smartsoltech.co.kr", + "response": "24시간 내 응답" + }, + "telegram": { + "title": "텔레그램", + "subtitle": "빠른 응답을 위해" + }, + "address": { + "title": "사무실 주소", + "line1": "서울시 강남구 테헤란로 123", + "line2": "대한민국" + }, + "cta": { + "ready": "준비되셨나요?", + "start": "시작하기", + "question": "궁금한 점이 있나요?", + "subtitle": "프로젝트에 대한 상담을 제공합니다" + }, + "meta": { + "title": "연락처", + "description": "프로젝트 문의나 상담을 위해 언제든지 연락주세요" + } + }, + "about": { + "hero": { + "title": "SmartSolTech 소개", + "subtitle": "혁신과 기술로 미래를 만들어갑니다" + }, + "company": { + "title": "회사 정보", + "description1": "SmartSolTech는 2020년에 설립된 기술 회사로, 웹 개발, 모바일 앱 개발, UI/UX 디자인 분야의 전문성으로 인정받고 있습니다.", + "description2": "고객의 니즈를 정확히 파악하고 최신 기술을 활용한 혁신적인 솔루션을 제공합니다." + }, + "stats": { + "projects": "완료된 프로젝트", + "experience": "년 경험", + "clients": "만족한 고객" + }, + "mission": { + "title": "우리의 미션", + "description": "기술을 통해 고객의 비즈니스 성장을 지원하고 디지털 혁신을 선도하는 것이 우리의 미션입니다." + }, + "values": { + "innovation": { + "title": "혁신", + "description": "지속적인 R&D와 최첨단 기술 도입으로 혁신적인 솔루션을 제공합니다." + }, + "quality": { + "title": "품질", + "description": "높은 품질 기준을 유지하며 고객이 만족할 수 있는 고품질 제품을 제공합니다." + }, + "partnership": { + "title": "파트너십", + "description": "고객과의 긴밀한 소통과 협력을 통해 최고의 결과를 만들어냅니다." + } + }, + "cta": { + "title": "함께 성장해나가겠습니다", + "subtitle": "아이디어를 현실로 바꿔보세요", + "button": "연락하기" + }, + "meta": { + "title": "회사소개", + "description": "SmartSolTech는 혁신적인 기술로 고객의 비즈니스 성장을 지원하는 전문 개발 회사입니다" + } + }, + "footer": { + "description": "혁신을 선도하는 디지털 솔루션 전문가", + "company": { + "description": "혁신을 선도하는 디지털 솔루션 전문가" + }, + "links": { + "title": "빠른 링크" + }, + "contact": { + "title": "연락처", + "email": "info@smartsoltech.co.kr", + "phone": "+82-2-1234-5678", + "address": "서울시 강남구 테헤란로 123" + }, + "copyright": "© {{year}} SmartSolTech. 모든 권리 보유.", + "privacy": "개인정보처리방침", + "terms": "이용약관" + }, + "theme": { + "light": "라이트 테마", + "dark": "다크 테마", + "toggle": "테마 전환" + }, + "language": { + "english": "English", + "korean": "한국어", + "russian": "Русский", + "kazakh": "Қазақша" + }, + "common": { + "loading": "로딩 중...", + "error": "오류가 발생했습니다", + "success": "성공", + "view_more": "더 보기", + "back": "뒤로", + "next": "다음", + "previous": "이전", + "view_details": "자세히 보기" + }, + "meta": { + "description": "SmartSolTech - 혁신적인 웹 개발, 모바일 앱 개발, UI/UX 디자인 서비스", + "keywords": "웹 개발, 모바일 앱, UI/UX 디자인, 한국", + "title": "SmartSolTech" + }, + "nav": { + "home": "홈", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "calculator": "비용계산기" + }, + "admin": { + "login": "관리자 패널 로그인", + "dashboard": "대시보드", + "title": "SmartSolTech 관리자" + }, + "company": { + "name": "SmartSolTech", + "description": "혁신을 선도하는 디지털 솔루션 전문가", + "email": "info@smartsoltech.kr", + "phone": "+82-10-1234-5678" + }, + "errors": { + "page_not_found": "페이지를 찾을 수 없습니다", + "error_occurred": "오류가 발생했습니다", + "title": "오류 - SmartSolTech", + "default_title": "오류가 발생했습니다", + "default_message": "요청을 처리하는 중 문제가 발생했습니다.", + "back_home": "홈으로 돌아가기", + "go_back": "뒤로 가기", + "need_help": "도움이 필요하신가요?", + "help_message": "문제가 지속되면 언제든지 저희에게 연락주세요.", + "contact_support": "고객지원 연락하기" + }, + "pages": { + "home": "홈페이지", + "about": "회사소개", + "services": "서비스", + "portfolio": "포트폴리오", + "contact": "연락처", + "calculator": "비용계산기" } } \ No newline at end of file diff --git a/.history/locales/ko_20251021211925.json b/locales/ko_backup.json similarity index 88% rename from .history/locales/ko_20251021211925.json rename to locales/ko_backup.json index ea46c6e..38ea21d 100644 --- a/.history/locales/ko_20251021211925.json +++ b/locales/ko_backup.json @@ -114,6 +114,42 @@ "title": "Check Your Project Estimate", "subtitle": "Select your desired services and requirements to calculate costs in real time", "button": "Use Cost Calculator" + }, + "step1": { + "title": "Choose Your Services", + "subtitle": "Select the services you need for your project" + }, + "step2": { + "title": "Project Details", + "subtitle": "Tell us about your project complexity and timeline" + }, + "complexity": { + "title": "Project Complexity", + "simple": "Simple", + "simple_desc": "Basic features and standard functionality", + "medium": "Medium", + "medium_desc": "Custom features and integrations", + "complex": "Complex", + "complex_desc": "Advanced features and complex integrations" + }, + "timeline": { + "title": "Project Timeline", + "standard": "Standard (4-8 weeks)", + "standard_desc": "Normal development timeline", + "rush": "Rush (2-4 weeks)", + "rush_desc": "Expedited delivery with additional cost", + "extended": "Extended (8+ weeks)", + "extended_desc": "Flexible timeline with cost optimization" + }, + "result": { + "title": "Project Estimate", + "subtitle": "Your estimated project cost", + "estimated_price": "Estimated Price", + "price_note": "This is an approximate cost. Final price may vary based on project complexity.", + "summary": "Project Summary", + "get_quote": "Get Detailed Quote", + "contact_note": "Contact us for a detailed consultation and precise quote.", + "recalculate": "Calculate Again" } }, "contact": { diff --git a/locales/ru.json b/locales/ru.json index 538c0a2..8b62965 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,241 +1,367 @@ { "navigation": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator", - "admin": "Admin" + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор", + "admin": "Админ" }, "hero": { "title": { - "smart": "Smart", - "solutions": "Solutions" + "smart": "Умные", + "solutions": "Решения" }, - "subtitle": "Grow your business with innovative technology", - "description": "Innovative web development, mobile apps, UI/UX design leading your business digital transformation", + "subtitle": "Развивайте свой бизнес с инновационными технологиями", + "description": "Инновационная веб-разработка, мобильные приложения, UI/UX дизайн для цифровой трансформации вашего бизнеса", "cta": { - "start": "Get Started", - "portfolio": "View Portfolio" + "start": "Начать", + "portfolio": "Смотреть портфолио" } }, "services": { "title": { - "our": "Our", - "services": "Services" + "our": "Наши", + "services": "Услуги" }, - "subtitle": "Professional development services to turn your ideas into reality", - "description": "Digital solutions completed with cutting-edge technology and creative ideas", - "view_all": "View All Services", + "subtitle": "Профессиональные услуги разработки для воплощения ваших идей в реальность", + "description": "Цифровые решения с использованием передовых технологий и творческих идей", + "view_all": "Посмотреть все услуги", "web": { - "title": "Web Development", - "description": "Responsive websites and web application development", - "price": "From $500" + "title": "Веб-разработка", + "description": "Адаптивные сайты и веб-приложения", + "price": "От $500" }, "mobile": { - "title": "Mobile Apps", - "description": "iOS and Android native app development", - "price": "From $1,000" + "title": "Мобильные приложения", + "description": "Разработка нативных приложений для iOS и Android", + "price": "От $1,000" }, "design": { - "title": "UI/UX Design", - "description": "User-centered interface and experience design", - "price": "From $300" + "title": "UI/UX Дизайн", + "description": "Дизайн интерфейсов и пользовательского опыта", + "price": "От $300" }, "marketing": { - "title": "Digital Marketing", - "description": "SEO, social media marketing, advertising management", - "price": "From $200" + "title": "Цифровой маркетинг", + "description": "SEO, SMM, управление рекламой", + "price": "От $200" }, "meta": { - "title": "Services", - "description": "Check out SmartSolTech's professional services. Web development, mobile apps, UI/UX design, digital marketing and other technology solutions.", - "keywords": "web development, mobile apps, UI/UX design, digital marketing, technology solutions, SmartSolTech" + "title": "Услуги", + "description": "Ознакомьтесь с профессиональными услугами SmartSolTech. Веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг и другие технологические решения.", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, цифровой маркетинг, технологические решения, SmartSolTech" }, "hero": { - "title": "Our", - "title_highlight": "Services", - "subtitle": "Support business growth with innovative technology" + "title": "Наши", + "title_highlight": "Услуги", + "subtitle": "Поддержка роста бизнеса с инновационными технологиями" }, "cards": { - "starting_price": "Starting Price", - "consultation": "consultation", - "contact": "Contact", - "calculate_cost": "Calculate Cost", - "popular": "Popular", - "coming_soon": "Services Coming Soon", - "coming_soon_desc": "We'll soon offer various services!" + "starting_price": "Начальная цена", + "consultation": "консультация", + "contact": "Связаться", + "calculate_cost": "Рассчитать стоимость", + "popular": "Популярно", + "coming_soon": "Услуги скоро появятся", + "coming_soon_desc": "Скоро мы предложим различные услуги!" }, "process": { - "title": "Project Implementation Process", - "subtitle": "We conduct projects with systematic and professional processes", - "consultation": { - "title": "Consultation and Planning", - "description": "Accurately understand customer requirements" + "title": "Процесс реализации проекта", + "subtitle": "Мы ведем проекты с системным и профессиональным подходом", + "step1": { + "title": "Консультация и планирование", + "description": "Точно понимаем требования клиента и планируем оптимальное решение" + }, + "step2": { + "title": "Дизайн и проектирование", + "description": "Проектируем интуитивный дизайн, ориентированный на пользователя, и надежную системную архитектуру" + }, + "step3": { + "title": "Разработка и реализация", + "description": "Разрабатываем эффективные и масштабируемые решения, используя новейшие технологии и лучшие практики" + }, + "step4": { + "title": "Тестирование и развертывание", + "description": "Обеспечиваем качество через тщательное тестирование и проводим стабильное развертывание" } + }, + "why_choose": { + "title": "Почему стоит выбрать SmartSolTech?", + "modern_tech": { + "title": "Использование современных технологий", + "description": "Всегда отслеживаем новейшие технологические тренды и используем проверенный технологический стек для предоставления футуристических решений." + }, + "expert_team": { + "title": "Экспертная команда", + "description": "Команда, состоящая из экспертов в каждой области, сотрудничает для обеспечения результатов высочайшего качества." + }, + "fast_response": { + "title": "Быстрое реагирование", + "description": "Завершаем проекты в установленные сроки благодаря быстрой коммуникации и эффективному управлению проектами." + }, + "continuous_support": { + "title": "Постоянная поддержка", + "description": "Поддерживаем долгосрочное партнерство через постоянное обслуживание и техническую поддержку даже после завершения проекта." + }, + "quality_guarantee": { + "title": "Гарантия качества", + "subtitle": "Для удовлетворения клиентов\nСервис высочайшего качества" + } + }, + "cta": { + "title": "Готовы начать проект?", + "subtitle": "Мы предложим оптимальное решение через бесплатную консультацию", + "free_consultation": "Подать заявку на бесплатную консультацию", + "calculate_cost": "Рассчитать стоимость", + "view_portfolio": "Посмотреть портфолио" } }, "portfolio": { "title": { - "recent": "Recent", - "projects": "Projects" + "recent": "Недавние", + "projects": "Проекты" }, - "subtitle": "Check out successfully completed projects", - "description": "Check out the projects completed for customer success", - "view_details": "View Details", - "view_all": "View All Portfolio", + "subtitle": "Ознакомьтесь с успешно завершенными проектами", + "description": "Посмотрите на проекты, выполненные для успеха клиентов", + "view_details": "Подробнее", + "view_all": "Посмотреть все портфолио", "categories": { - "all": "All", - "web": "Web Development", - "mobile": "Mobile Apps", - "uiux": "UI/UX Design" + "all": "Все", + "web": "Веб-разработка", + "mobile": "Мобильные приложения", + "uiux": "UI/UX Дизайн" }, - "project_details": "Project Details", + "project_details": "Детали проекта", "default": { - "ecommerce": "E-commerce", - "title": "E-commerce Platform", - "description": "Modern online commerce solution with intuitive interface" + "ecommerce": "Электронная коммерция", + "title": "Платформа электронной коммерции", + "description": "Современное решение для интернет-торговли с интуитивным интерфейсом" }, "meta": { - "title": "Portfolio", - "description": "Check out SmartSolTech's diverse projects and success stories. Web development, mobile apps, UI/UX design portfolio.", - "keywords": "portfolio, web development, mobile apps, UI/UX design, projects, SmartSolTech" + "title": "Портфолио", + "description": "Ознакомьтесь с разнообразными проектами и историями успеха SmartSolTech. Портфолио веб-разработки, мобильных приложений, UI/UX дизайна.", + "keywords": "портфолио, веб-разработка, мобильные приложения, UI/UX дизайн, проекты, SmartSolTech" + } + }, + "portfolio_page": { + "title": "Наше портфолио", + "subtitle": "Откройте для себя инновационные проекты и креативные решения", + "categories": { + "all": "Все", + "web-development": "Веб-разработка", + "mobile-app": "Мобильные приложения", + "ui-ux-design": "UI/UX Дизайн", + "branding": "Брендинг", + "marketing": "Цифровой маркетинг" + }, + "buttons": { + "details": "Подробнее", + "projectDetails": "Детали проекта", + "loadMore": "Загрузить больше проектов", + "contact": "Заказать проект", + "calculate": "Рассчитать стоимость" + }, + "empty": { + "title": "Портфолио пока пусто", + "subtitle": "Скоро мы представим потрясающие проекты!" + }, + "cta": { + "title": "Станьте звездой следующего проекта", + "subtitle": "Создавайте инновационные цифровые решения вместе с нами" + }, + "labels": { + "featured": "РЕКОМЕНДУЕМОЕ", + "views": "просмотров", + "likes": "лайков" } }, "calculator": { - "title": "Project Cost Calculator", - "subtitle": "Select your desired services and requirements to get accurate cost estimates in real time", + "title": "Калькулятор стоимости проекта", + "subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в реальном времени", + "next_step": "Следующий шаг", + "prev_step": "Предыдущий шаг", + "calculate": "Рассчитать", + "reset": "Сброс", + "live_update": "Обновление в реальном времени", "meta": { - "title": "Project Cost Calculator", - "description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator" + "title": "Калькулятор стоимости проекта", + "description": "Рассчитайте стоимость вашего проекта веб-разработки, мобильного приложения или дизайна с помощью нашего интерактивного калькулятора" }, "cta": { - "title": "Check Your Project Estimate", - "subtitle": "Select your desired services and requirements to calculate costs in real time", - "button": "Use Cost Calculator" + "title": "Проверьте оценку вашего проекта", + "subtitle": "Выберите нужные услуги и требования для расчета стоимости в реальном времени", + "button": "Использовать калькулятор стоимости" + }, + "step1": { + "title": "Выберите ваши услуги", + "nav_title": "Шаг 1: Услуги", + "subtitle": "Выберите услуги, необходимые для вашего проекта" + }, + "step2": { + "title": "Детали проекта", + "nav_title": "Шаг 2: Детали", + "subtitle": "Расскажите нам о сложности и сроках вашего проекта" + }, + "complexity": { + "title": "Сложность проекта", + "simple": "Простой", + "simple_desc": "Базовые функции и стандартный функционал", + "medium": "Средний", + "medium_desc": "Пользовательские функции и интеграции", + "complex": "Сложный", + "complex_desc": "Продвинутые функции и сложные интеграции" + }, + "timeline": { + "title": "Сроки проекта", + "standard": "Стандартные (4-8 недель)", + "standard_desc": "Обычные сроки разработки", + "rush": "Срочные (2-4 недели)", + "rush_desc": "Ускоренная доставка с дополнительной стоимостью", + "extended": "Расширенные (8+ недель)", + "extended_desc": "Гибкие сроки с оптимизацией стоимости" + }, + "result": { + "title": "Оценка проекта", + "subtitle": "Ваша расчетная стоимость проекта", + "nav_title": "Результат", + "estimated_price": "Расчетная цена", + "price_note": "Это приблизительная стоимость. Итоговая цена может варьироваться в зависимости от сложности проекта.", + "summary": "Сводка проекта", + "get_quote": "Получить подробное предложение", + "contact_note": "Свяжитесь с нами для подробной консультации и точного предложения.", + "recalculate": "Рассчитать снова", + "selected_services": "Выбранные услуги", + "complexity": "Сложность", + "timeline": "Сроки" } }, "contact": { "hero": { - "title": "Contact Us", - "subtitle": "We're here to help bring your ideas to life" + "title": "Свяжитесь с нами", + "subtitle": "Мы здесь, чтобы помочь воплотить ваши идеи в жизнь" }, - "ready_title": "Ready to Start Your Project?", - "ready_description": "Turn your ideas into reality. Experts provide the best solutions.", + "ready_title": "Готовы начать свой проект?", + "ready_description": "Превратите свои идеи в реальность. Эксперты предоставят лучшие решения.", "form": { - "title": "Project Inquiry", - "name": "Name", + "title": "Запрос проекта", + "name": "Имя", "email": "Email", - "phone": "Phone", - "message": "Message", - "submit": "Send Inquiry", - "success": "Inquiry sent successfully", - "error": "Error occurred while sending inquiry", + "phone": "Телефон", + "message": "Сообщение", + "submit": "Отправить запрос", + "success": "Запрос успешно отправлен", + "error": "Произошла ошибка при отправке запроса", "service": { - "title": "Service Interest", - "select": "Select service of interest", - "web": "Web Development", - "mobile": "Mobile App", - "design": "UI/UX Design", - "branding": "Branding", - "consulting": "Consulting", - "other": "Other" + "title": "Интересующая услуга", + "select": "Выберите интересующую услугу", + "web": "Веб-разработка", + "mobile": "Мобильное приложение", + "design": "UI/UX Дизайн", + "branding": "Брендинг", + "consulting": "Консультирование", + "other": "Другое" } }, "info": { - "title": "Contact Information" + "title": "Контактная информация" }, "phone": { - "title": "Phone Inquiry", + "title": "Телефонный запрос", "number": "+82-2-1234-5678", - "hours": "Mon-Fri 9:00-18:00" + "hours": "Пн-Пт 9:00-18:00" }, "email": { - "title": "Email Inquiry", + "title": "Email запрос", "address": "info@smartsoltech.co.kr", - "response": "Response within 24 hours" + "response": "Ответ в течение 24 часов" }, "telegram": { "title": "Telegram", - "subtitle": "For quick response" + "subtitle": "Для быстрого ответа" }, "address": { - "title": "Office Address", - "line1": "123 Teheran-ro, Gangnam-gu", - "line2": "Seoul, South Korea" + "title": "Адрес офиса", + "line1": "ул. Тегеран-ро, 123, Каннам-гу", + "line2": "Сеул, Южная Корея" }, "cta": { - "ready": "Ready?", - "start": "Get Started", - "question": "Have questions?", - "subtitle": "We provide consultation on projects" + "ready": "Готовы?", + "start": "Начать", + "question": "Есть вопросы?", + "subtitle": "Мы предоставляем консультации по проектам" }, "meta": { - "title": "Contact", - "description": "Contact us anytime for project inquiries or consultation" + "title": "Контакты", + "description": "Свяжитесь с нами в любое время для запросов по проектам или консультации" } }, "about": { "hero": { - "title": "About SmartSolTech", - "subtitle": "Creating the future with innovation and technology" + "title": "О SmartSolTech", + "subtitle": "Создаем будущее с инновациями и технологиями" }, "company": { - "title": "Company Information", - "description1": "SmartSolTech is a technology company established in 2020, recognized for expertise in web development, mobile app development, and UI/UX design.", - "description2": "We accurately understand customer needs and provide innovative solutions using the latest technology." + "title": "Информация о компании", + "description1": "SmartSolTech - технологическая компания, основанная в 2020 году, признанная за экспертизу в веб-разработке, разработке мобильных приложений и UI/UX дизайне.", + "description2": "Мы точно понимаем потребности клиентов и предоставляем инновационные решения, используя новейшие технологии." }, "stats": { - "projects": "Completed Projects", - "experience": "Years Experience", - "clients": "Satisfied Customers" + "projects": "Завершенных проектов", + "experience": "Лет опыта", + "clients": "Довольных клиентов" }, "mission": { - "title": "Our Mission", - "description": "Our mission is to support customer business growth through technology and lead digital innovation." + "title": "Наша миссия", + "description": "Наша миссия - поддерживать рост бизнеса клиентов через технологии и лидировать в цифровых инновациях." }, "values": { "innovation": { - "title": "Innovation", - "description": "We provide innovative solutions through continuous R&D and adoption of cutting-edge technology." + "title": "Инновации", + "description": "Мы предоставляем инновационные решения через постоянные исследования и внедрение передовых технологий." }, "quality": { - "title": "Quality", - "description": "We maintain high quality standards and provide high-quality products that customers can be satisfied with." + "title": "Качество", + "description": "Мы поддерживаем высокие стандарты качества и предоставляем высококачественные продукты, которыми клиенты могут быть довольны." }, "partnership": { - "title": "Partnership", - "description": "We create the best results through close communication and collaboration with customers." + "title": "Партнерство", + "description": "Мы создаем лучшие результаты через тесное общение и сотрудничество с клиентами." } }, "cta": { - "title": "We'll Grow Together", - "subtitle": "Turn your ideas into reality", - "button": "Contact Us" + "title": "Мы будем расти вместе", + "subtitle": "Превратите свои идеи в реальность", + "button": "Связаться с нами" }, "meta": { - "title": "About Us", - "description": "SmartSolTech is a professional development company that supports customer business growth with innovative technology" + "title": "О нас", + "description": "SmartSolTech - профессиональная компания разработки, которая поддерживает рост бизнеса клиентов инновационными технологиями" } }, "footer": { - "description": "Digital solution specialist leading innovation", + "description": "Специалист цифровых решений, лидирующий в инновациях", + "company": { + "description": "Специалист цифровых решений, лидирующий в инновациях" + }, "links": { - "title": "Quick Links" + "title": "Быстрые ссылки" }, "contact": { - "title": "Contact", + "title": "Контакты", "email": "info@smartsoltech.co.kr", "phone": "+82-2-1234-5678", - "address": "123 Teheran-ro, Gangnam-gu, Seoul" + "address": "ул. Тегеран-ро, 123, Каннам-гу, Сеул" }, - "copyright": "© 2024 SmartSolTech. All rights reserved." + "copyright": "© {{year}} SmartSolTech. Все права защищены.", + "privacy": "Политика конфиденциальности", + "terms": "Условия использования" }, "theme": { - "light": "Light Theme", - "dark": "Dark Theme", - "toggle": "Toggle Theme" + "light": "Светлая тема", + "dark": "Темная тема", + "toggle": "Переключить тему" }, "language": { "english": "English", @@ -244,88 +370,56 @@ "kazakh": "Қазақша" }, "common": { - "loading": "Loading...", - "error": "Error occurred", - "success": "Success", - "view_more": "View More", - "back": "Back", - "next": "Next", - "previous": "Previous", - "view_details": "View Details" + "loading": "Загрузка...", + "error": "Произошла ошибка", + "success": "Успешно", + "view_more": "Смотреть больше", + "back": "Назад", + "next": "Далее", + "previous": "Предыдущий", + "view_details": "Подробнее" }, "meta": { - "description": "SmartSolTech - Innovative web development, mobile app development, UI/UX design services", - "keywords": "web development, mobile apps, UI/UX design, Korea", + "description": "SmartSolTech - Инновационная веб-разработка, разработка мобильных приложений, UI/UX дизайн", + "keywords": "веб-разработка, мобильные приложения, UI/UX дизайн, Корея", "title": "SmartSolTech" }, "nav": { - "home": "Home", - "about": "About", - "services": "Services", - "portfolio": "Portfolio", - "calculator": "Calculator" + "home": "Главная", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "calculator": "Калькулятор" }, "admin": { - "login": "Admin Panel Login", - "dashboard": "Dashboard", - "title": "SmartSolTech Admin" + "login": "Вход в админ-панель", + "dashboard": "Панель управления", + "title": "SmartSolTech Админ" }, "company": { "name": "SmartSolTech", - "description": "Digital solution specialist leading innovation", + "description": "Специалист цифровых решений, лидирующий в инновациях", "email": "info@smartsoltech.kr", "phone": "+82-10-1234-5678" }, "errors": { - "page_not_found": "Page not found", - "error_occurred": "Error occurred", - "title": "Error - SmartSolTech", - "default_title": "An Error Occurred", - "default_message": "A problem occurred while processing the request.", - "back_home": "Back to Home", - "go_back": "Go Back", - "need_help": "Need Help?", - "help_message": "If the problem persists, please contact us anytime.", - "contact_support": "Contact Support" + "page_not_found": "Страница не найдена", + "error_occurred": "Произошла ошибка", + "title": "Ошибка - SmartSolTech", + "default_title": "Произошла ошибка", + "default_message": "Возникла проблема при обработке запроса.", + "back_home": "Вернуться на главную", + "go_back": "Вернуться", + "need_help": "Нужна помощь?", + "help_message": "Если проблема повторяется, свяжитесь с нами в любое время.", + "contact_support": "Связаться с поддержкой" }, "pages": { - "home": "Home page", - "about": "About us", - "services": "Services", - "portfolio": "Portfolio", - "contact": "Contact", - "calculator": "Calculator" - }, - "portfolio_page": { - "title": "Our Portfolio", - "subtitle": "Discover innovative projects and creative solutions", - "categories": { - "all": "All", - "web-development": "Web Development", - "mobile-app": "Mobile App", - "ui-ux-design": "UI/UX Design", - "branding": "Branding", - "marketing": "Digital Marketing" - }, - "buttons": { - "details": "View Details", - "projectDetails": "Project Details", - "loadMore": "Load More Projects", - "contact": "Request Project", - "calculate": "Calculate Cost" - }, - "empty": { - "title": "No portfolio yet", - "subtitle": "We'll be showcasing amazing projects soon!" - }, - "cta": { - "title": "Be the star of the next project", - "subtitle": "Create innovative digital solutions with us" - }, - "labels": { - "featured": "FEATURED", - "views": "views", - "likes": "likes" - } + "home": "Главная страница", + "about": "О нас", + "services": "Услуги", + "portfolio": "Портфолио", + "contact": "Контакты", + "calculator": "Калькулятор" } } \ No newline at end of file diff --git a/.history/locales/en_20251021211404.json b/locales/ru_backup.json similarity index 76% rename from .history/locales/en_20251021211404.json rename to locales/ru_backup.json index 64bfc8b..bfc49c1 100644 --- a/.history/locales/en_20251021211404.json +++ b/locales/ru_backup.json @@ -104,16 +104,52 @@ } }, "calculator": { - "title": "Project Cost Calculator", - "subtitle": "Select your desired services and requirements to get accurate cost estimates in real time", + "title": "Калькулятор стоимости проекта", + "subtitle": "Выберите нужные услуги и требования для получения точной оценки стоимости в режиме реального времени", "meta": { - "title": "Project Cost Calculator", - "description": "Calculate the cost of your web development, mobile app, or design project with our interactive calculator" + "title": "Калькулятор стоимости проекта", + "description": "Рассчитайте стоимость вашего веб-разработки, мобильного приложения или дизайн-проекта с помощью нашего интерактивного калькулятора" }, "cta": { - "title": "Check Your Project Estimate", - "subtitle": "Select your desired services and requirements to calculate costs in real time", - "button": "Use Cost Calculator" + "title": "Проверьте оценку вашего проекта", + "subtitle": "Выберите нужные услуги и требования для расчета стоимости в режиме реального времени", + "button": "Использовать калькулятор стоимости" + }, + "step1": { + "title": "Выберите ваши услуги", + "subtitle": "Выберите услуги, которые нужны для вашего проекта" + }, + "step2": { + "title": "Детали проекта", + "subtitle": "Расскажите нам о сложности и сроках вашего проекта" + }, + "complexity": { + "title": "Сложность проекта", + "simple": "Простой", + "simple_desc": "Базовые функции и стандартный функционал", + "medium": "Средний", + "medium_desc": "Пользовательские функции и интеграции", + "complex": "Сложный", + "complex_desc": "Продвинутые функции и сложные интеграции" + }, + "timeline": { + "title": "Сроки проекта", + "standard": "Стандартные (4-8 недель)", + "standard_desc": "Обычные сроки разработки", + "rush": "Срочные (2-4 недели)", + "rush_desc": "Ускоренная доставка с дополнительной стоимостью", + "extended": "Расширенные (8+ недель)", + "extended_desc": "Гибкие сроки с оптимизацией стоимости" + }, + "result": { + "title": "Оценка проекта", + "subtitle": "Ваша расчетная стоимость проекта", + "estimated_price": "Расчетная цена", + "price_note": "Это приблизительная стоимость. Итоговая цена может варьироваться в зависимости от сложности проекта.", + "summary": "Сводка проекта", + "get_quote": "Получить подробное предложение", + "contact_note": "Свяжитесь с нами для подробной консультации и точного предложения.", + "recalculate": "Рассчитать снова" } }, "contact": { @@ -301,7 +337,7 @@ "subtitle": "Discover innovative projects and creative solutions", "categories": { "all": "All", - "web-development": "Web Development", + "web-development": "Web Development", "mobile-app": "Mobile App", "ui-ux-design": "UI/UX Design", "branding": "Branding", diff --git a/package-lock.json b/package-lock.json index 652d598..96c1388 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,18 +36,33 @@ "socket.io": "^4.7.4" }, "devDependencies": { + "autoprefixer": "^10.4.21", "css-loader": "^6.8.1", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.3", "mini-css-extract-plugin": "^2.7.6", "nodemon": "^3.0.2", + "postcss": "^8.5.6", "style-loader": "^3.3.3", + "tailwindcss": "^3.4.13", "terser-webpack-plugin": "^5.3.9", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", "workbox-webpack-plugin": "^7.0.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -2037,6 +2052,50 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2131,6 +2190,51 @@ "make-plural": "^7.0.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -2695,6 +2799,24 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2713,6 +2835,12 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -2813,6 +2941,43 @@ "node": ">= 4.0.0" } }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3152,6 +3317,15 @@ "tslib": "^2.0.3" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001751", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", @@ -3718,6 +3892,18 @@ "node": ">=8" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -3821,6 +4007,12 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -3864,6 +4056,12 @@ "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", "dev": true }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -4331,6 +4529,22 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4369,6 +4583,15 @@ "node": ">= 4.9.1" } }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -4511,6 +4734,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4542,6 +4781,19 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -5302,6 +5554,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -5583,6 +5844,21 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", @@ -5638,6 +5914,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5792,6 +6077,21 @@ "node": ">=6" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", @@ -5953,6 +6253,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -5961,6 +6270,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -6092,6 +6414,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6184,6 +6515,17 @@ "mustache": "bin/mustache" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6332,6 +6674,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -6361,6 +6712,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -6478,6 +6838,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -6539,6 +6905,28 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -6659,6 +7047,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -6707,6 +7113,95 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", @@ -6766,6 +7261,44 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", @@ -6917,6 +7450,26 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -6956,6 +7509,15 @@ "node": ">= 0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -7296,6 +7858,16 @@ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==" }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "2.79.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", @@ -7311,6 +7883,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -7764,6 +8359,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-swizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", @@ -8013,6 +8620,71 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -8119,6 +8791,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", @@ -8144,6 +8829,81 @@ "webpack": "^5.0.0" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -8168,6 +8928,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwindcss": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -8319,6 +9141,27 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -8389,6 +9232,12 @@ "punycode": "^2.1.0" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -9336,6 +10185,103 @@ "workbox-core": "7.3.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -9374,6 +10320,18 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } } } } diff --git a/package.json b/package.json index 8db9954..2ca1086 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,13 @@ "scripts": { "start": "node server.js", "demo": "node server-demo.js", - "dev": "node scripts/dev.js", - "build": "node scripts/build.js", + "dev": "npm run css:dev && node scripts/dev.js", + "build": "npm run css:prod && node scripts/build.js", "init-db": "node scripts/init-db.js", "test": "echo \"Error: no test specified\" && exit 1", - "sync-locales": "node scripts/sync-locales.js" + "sync-locales": "node scripts/sync-locales.js", + "css:dev": "tailwindcss -i ./src/tailwind.css -o ./public/css/tailwind.css --watch", + "css:prod": "tailwindcss -i ./src/tailwind.css -o ./public/css/tailwind.css --minify" }, "keywords": [ "pwa", @@ -50,12 +52,15 @@ "socket.io": "^4.7.4" }, "devDependencies": { + "autoprefixer": "^10.4.21", "css-loader": "^6.8.1", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.3", "mini-css-extract-plugin": "^2.7.6", "nodemon": "^3.0.2", + "postcss": "^8.5.6", "style-loader": "^3.3.3", + "tailwindcss": "^3.4.13", "terser-webpack-plugin": "^5.3.9", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..96bb01e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css index 8858763..2cfc67d 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -465,89 +465,408 @@ body { /* Calculator Styles */ .calculator-step { display: none; + animation: fadeInUp 0.5s ease-in-out; } .calculator-step.active { display: block; } +@keyframes fadeInUp { + 0% { + opacity: 0; + transform: translateY(20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +/* Step Indicators */ +.calculator-progress-wrapper { + position: relative; +} + +.step-indicator { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + z-index: 2; +} + +.step-number { + width: 40px; + height: 40px; + border-radius: 50%; + background: #e5e7eb; + color: #6b7280; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + margin-bottom: 8px; + transition: all 0.3s ease; +} + +.step-indicator.active .step-number { + background: linear-gradient(135deg, #3B82F6, #8B5CF6); + color: white; + transform: scale(1.1); +} + +.step-indicator.completed .step-number { + background: linear-gradient(135deg, #10B981, #059669); + color: white; + transform: scale(1.05); +} + +.step-indicator.completed .step-number::after { + content: '✓'; + position: absolute; + font-size: 14px; + font-weight: bold; +} + +.step-label { + font-size: 0.875rem; + color: #6b7280; + font-weight: 500; + text-align: center; + transition: color 0.3s ease; +} + +.step-indicator.active .step-label { + color: #3B82F6; + font-weight: 600; +} + +.step-indicator.completed .step-label { + color: #10B981; + font-weight: 600; +} + +.step-connector { + flex: 1; + height: 2px; + background: #e5e7eb; + margin: 0 1rem; + position: relative; + top: -15px; +} + +/* Selection indicators */ +.selection-indicator { + transition: all 0.3s ease; +} + +.service-option.selected .selection-indicator, +.complexity-option.selected .selection-indicator, +.timeline-option.selected .selection-indicator { + opacity: 1 !important; +} + +.service-option.selected .selection-indicator .w-6, +.complexity-option.selected .selection-indicator .w-6, +.timeline-option.selected .selection-indicator .w-6 { + background: #3B82F6; + border-color: #3B82F6; + position: relative; +} + +.service-option.selected .selection-indicator .w-6::after, +.complexity-option.selected .selection-indicator .w-6::after, +.timeline-option.selected .selection-indicator .w-6::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 12px; + font-weight: bold; +} + .service-option, .complexity-option, .timeline-option { cursor: pointer; transition: all 0.3s ease; position: relative; + border-width: 2px; +} + +/* Calculator specific improvements */ +.calculator-step { + width: 100%; +} + +/* Better spacing and padding for calculator */ +.calculator-step .service-option, +.calculator-step .complexity-option, +.calculator-step .timeline-option { + margin: 0.5rem 0; + padding: 1.5rem; +} + +/* Ensure text doesn't touch edges */ +.calculator-step h2, +.calculator-step h3, +.calculator-step h4, +.calculator-step p { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +/* Responsive padding for service options */ +@media (max-width: 640px) { + .calculator-step .service-option, + .calculator-step .complexity-option, + .calculator-step .timeline-option { + padding: 1rem; + margin: 0.25rem 0; + } + + .calculator-step .space-y-4 > *, + .calculator-step .space-y-8 > * { + margin-top: 0.75rem; + } +} + +/* Compact service options for full width layout */ +.service-option.compact { + padding: 1rem 1.5rem; + border-radius: 12px; + margin-bottom: 0.5rem; +} + +.service-option.compact .w-14 { + width: 3rem; + height: 3rem; +} + +.service-option.compact h3 { + font-size: 1rem; + margin-bottom: 0.25rem; +} + +.service-option.compact p { + font-size: 0.875rem; + line-height: 1.4; +} + +/* Table-style layout for complexity and timeline */ +.complexity-option, +.timeline-option { + min-height: 140px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + +.complexity-option .w-16, +.timeline-option .w-16 { + width: 3.5rem; + height: 3.5rem; + margin: 0 auto 1rem auto; +} + +.complexity-option h4, +.timeline-option h4 { + font-size: 1.1rem; + margin-bottom: 0.75rem; +} + +.complexity-option p, +.timeline-option p { + font-size: 0.9rem; + line-height: 1.4; + margin-bottom: 0.5rem; +} + +/* Enhanced button styles */ +.next-step, +.prev-step, +.restart-calculator { + font-weight: 600; + letter-spacing: 0.025em; + white-space: nowrap; +} + +/* Better result card styling */ +#step-3 .relative { + max-width: none; +} + +#step-3 #final-price { + word-break: break-word; +} + +/* Service Options Hover Effects */ +.service-option[data-service="web"]:hover { + border-color: #3B82F6 !important; +} + +.service-option[data-service="mobile"]:hover { + border-color: #10B981 !important; +} + +.service-option[data-service="design"]:hover { + border-color: #8B5CF6 !important; +} + +.service-option[data-service="marketing"]:hover { + border-color: #F59E0B !important; +} + +/* Complexity Options Hover Effects */ +.complexity-option[data-value="simple"]:hover { + border-color: #10B981 !important; +} + +.complexity-option[data-value="medium"]:hover { + border-color: #F59E0B !important; +} + +.complexity-option[data-value="complex"]:hover { + border-color: #EF4444 !important; +} + +/* Timeline Options Hover Effects */ +.timeline-option[data-value="standard"]:hover { + border-color: #3B82F6 !important; +} + +.timeline-option[data-value="rush"]:hover { + border-color: #F97316 !important; +} + +.timeline-option[data-value="extended"]:hover { + border-color: #059669 !important; } .service-option:hover, .complexity-option:hover, .timeline-option:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15); + transform: translateY(-4px); + box-shadow: 0 12px 40px rgba(59, 130, 246, 0.15); +} + +/* Selected states with specific colors */ +.service-option[data-service="web"].selected { + border-color: #3B82F6 !important; +} + +.service-option[data-service="mobile"].selected { + border-color: #10B981 !important; +} + +.service-option[data-service="design"].selected { + border-color: #8B5CF6 !important; +} + +.service-option[data-service="marketing"].selected { + border-color: #F59E0B !important; +} + +.complexity-option[data-value="simple"].selected { + border-color: #10B981 !important; +} + +.complexity-option[data-value="medium"].selected { + border-color: #F59E0B !important; +} + +.complexity-option[data-value="complex"].selected { + border-color: #EF4444 !important; +} + +.timeline-option[data-value="standard"].selected { + border-color: #3B82F6 !important; +} + +.timeline-option[data-value="rush"].selected { + border-color: #F97316 !important; +} + +.timeline-option[data-value="extended"].selected { + border-color: #059669 !important; } .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); + background: linear-gradient(135deg, #EBF8FF, #F0F9FF) !important; + transform: translateY(-4px); + box-shadow: 0 12px 40px rgba(59, 130, 246, 0.2); } -.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; +/* Dark mode adjustments for selected states */ +.dark .service-option.selected, +.dark .complexity-option.selected, +.dark .timeline-option.selected { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(139, 92, 246, 0.1)) !important; + border-color: #60A5FA !important; } /* Price Display Animation */ #final-price { - animation: priceReveal 0.8s ease-in-out; + animation: priceReveal 1.2s ease-in-out; } @keyframes priceReveal { 0% { opacity: 0; - transform: scale(0.8); + transform: scale(0.8) rotateY(180deg); } 50% { - transform: scale(1.1); + transform: scale(1.1) rotateY(90deg); } 100% { opacity: 1; - transform: scale(1); + transform: scale(1) rotateY(0deg); } } /* Calculator Progress Bar */ .calculator-progress { width: 100%; - height: 4px; + height: 6px; background: #e5e7eb; - border-radius: 2px; - margin-bottom: 2rem; + border-radius: 3px; overflow: hidden; + position: relative; } .calculator-progress-bar { height: 100%; - background: linear-gradient(90deg, #3B82F6, #8B5CF6); - border-radius: 2px; - transition: width 0.3s ease; + background: linear-gradient(90deg, #3B82F6, #8B5CF6, #06B6D4); + border-radius: 3px; + transition: width 0.6s ease-in-out; + width: 33.33%; + position: relative; + overflow: hidden; +} + +.calculator-progress-bar::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); + animation: progressShine 2s ease-in-out infinite; +} + +@keyframes progressShine { + 0% { left: -100%; } + 100% { left: 100%; } +} + +.calculator-progress-bar.step-1 { width: 33.33%; } @@ -559,6 +878,11 @@ body { width: 100%; } +/* Button hover effects */ +.group:hover .fas { + transform: translateX(2px); +} + /* Calculator Mobile Improvements */ @media (max-width: 768px) { .service-option, @@ -568,7 +892,27 @@ body { } #final-price { - font-size: 2rem; + font-size: 2.5rem; + } + + .step-connector { + display: none; + } + + .calculator-progress-wrapper .flex { + flex-direction: column; + gap: 1rem; + } + + .step-indicator { + flex-direction: row; + gap: 0.5rem; + } + + .step-number { + width: 32px; + height: 32px; + margin-bottom: 0; } } diff --git a/public/css/sticky-price.css b/public/css/sticky-price.css new file mode 100644 index 0000000..d6ea963 --- /dev/null +++ b/public/css/sticky-price.css @@ -0,0 +1,197 @@ +/* Independent Floating Price Island */ +#stickyPriceContainer { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 45; + display: none; + animation: slideUpIn 0.3s ease-out; + max-width: 56rem; /* max-w-4xl = 896px - match calculator width */ + width: calc(100% - 2rem); + + /* Enhanced backdrop blur effect */ + -webkit-backdrop-filter: blur(12px) saturate(180%); + backdrop-filter: blur(12px) saturate(180%); + + /* Smooth transitions */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sticky-price-island { + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 1rem; + transition: all 0.3s ease; +} + +.dark .sticky-price-island { + background: rgba(31, 41, 55, 0.95); + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.sticky-price-island:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.dark .sticky-price-island:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); +} + +#stickyPriceContainer.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +#stickyPriceContainer.hide { + transform: translateX(-50%) translateY(100%); + opacity: 0; +} + +/* Price breakdown sections */ +.price-breakdown-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.dark .price-breakdown-item { + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +.price-breakdown-item:last-child { + border-bottom: none; + margin-top: 0.5rem; + padding-top: 0.75rem; + border-top: 2px solid rgba(59, 130, 246, 0.2); + font-weight: 600; +} + +.breakdown-label { + font-size: 0.875rem; + color: #6b7280; + transition: color 0.2s ease; +} + +.dark .breakdown-label { + color: #9ca3af; +} + +.breakdown-value { + font-weight: 500; + color: #374151; + transition: color 0.2s ease; +} + +.dark .breakdown-value { + color: #f3f4f6; +} + +.breakdown-multiplier { + color: #059669; + font-size: 0.875rem; +} + +.dark .breakdown-multiplier { + color: #10b981; +} + +.breakdown-discount { + color: #dc2626; + font-size: 0.875rem; +} + +.dark .breakdown-discount { + color: #ef4444; +} + +/* Final price styling */ +.final-price-value { + color: #1f2937; + font-size: 1.125rem; + font-weight: 700; +} + +.dark .final-price-value { + color: #f9fafb; +} + +/* Price update animation */ +#currentPrice, #finalCalculation { + transition: all 0.2s ease; +} + +.price-update { + animation: priceUpdate 0.3s ease-out; +} + +@keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); color: #3b82f6; } + 100% { transform: scale(1); } +} + +/* Animation */ +@keyframes slideUpIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + #stickyPriceContainer { + bottom: 0.5rem; + max-width: calc(100% - 2rem); /* Full width minus padding on mobile */ + width: calc(100% - 2rem); + } + + .sticky-price-island { + padding: 0.75rem; + border-radius: 12px; + } + + .price-breakdown-item { + padding: 0.375rem 0; + } + + .breakdown-label { + font-size: 0.8125rem; + } + + .final-price-value { + font-size: 1rem; + } +} + +/* Hide on very small screens to avoid overlap */ +@media (max-width: 380px) { + #stickyPriceContainer { + display: none !important; + } +} + +/* Blur effect support fallback */ +@supports not (backdrop-filter: blur(12px)) { + .sticky-price-island { + background: rgba(255, 255, 255, 0.95); + } + + .dark .sticky-price-island { + background: rgba(17, 24, 39, 0.95); + } +} \ No newline at end of file diff --git a/public/css/tailwind.css b/public/css/tailwind.css new file mode 100644 index 0000000..a12c414 --- /dev/null +++ b/public/css/tailwind.css @@ -0,0 +1,3898 @@ +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +/* +! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: Ubuntu, system-ui, sans-serif; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +html { + scroll-behavior: smooth; +} + +body { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + font-family: Ubuntu, system-ui, sans-serif; + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +h1, h2, h3, h4, h5, h6 { + font-family: Ubuntu, system-ui, sans-serif; + font-weight: 600; +} + +.container { + margin-left: auto; + margin-right: auto; + max-width: 80rem; + padding-left: 1rem; + padding-right: 1rem; +} + +@media (min-width: 640px) { + .container { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} + +@media (min-width: 1024px) { + .container { + padding-left: 2rem; + padding-right: 2rem; + } +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.375rem; + border-width: 1px; + border-color: transparent; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 500; + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; +} + +.btn:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-ring-offset-width: 2px; +} + +.btn-primary { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.btn-primary:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.btn-primary:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); +} + +.btn-secondary { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.btn-secondary:hover { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.btn-secondary:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity)); +} + +.card { + border-radius: 0.5rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.form-input { + display: block; + width: 100%; + border-radius: 0.375rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); + padding-left: 0.75rem; + padding-right: 0.75rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.form-input::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.form-input::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.form-input { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.form-input:focus { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); +} + +@media (min-width: 640px) { + .form-input { + font-size: 0.75rem; + line-height: 1rem; + } +} + +.badge { + display: inline-flex; + align-items: center; + border-radius: 9999px; + padding-left: 0.625rem; + padding-right: 0.625rem; + padding-top: 0.125rem; + padding-bottom: 0.125rem; + font-size: 0.625rem; + line-height: 0.875rem; + font-weight: 500; +} + +.alert { + border-radius: 0.375rem; + border-width: 1px; + padding: 1rem; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.visible { + visibility: visible; +} + +.invisible { + visibility: hidden; +} + +.collapse { + visibility: collapse; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: sticky; +} + +.inset-0 { + inset: 0px; +} + +.inset-y-0 { + top: 0px; + bottom: 0px; +} + +.-bottom-4 { + bottom: -1rem; +} + +.-bottom-40 { + bottom: -10rem; +} + +.-left-32 { + left: -8rem; +} + +.-left-8 { + left: -2rem; +} + +.-right-32 { + right: -8rem; +} + +.-right-4 { + right: -1rem; +} + +.-top-40 { + top: -10rem; +} + +.-top-8 { + top: -2rem; +} + +.bottom-0 { + bottom: 0px; +} + +.bottom-4 { + bottom: 1rem; +} + +.bottom-8 { + bottom: 2rem; +} + +.left-0 { + left: 0px; +} + +.left-1 { + left: 0.25rem; +} + +.left-1\/2 { + left: 50%; +} + +.left-2 { + left: 0.5rem; +} + +.left-4 { + left: 1rem; +} + +.left-40 { + left: 10rem; +} + +.right-0 { + right: 0px; +} + +.right-1 { + right: 0.25rem; +} + +.right-2 { + right: 0.5rem; +} + +.right-3 { + right: 0.75rem; +} + +.right-4 { + right: 1rem; +} + +.top-0 { + top: 0px; +} + +.top-0\.5 { + top: 0.125rem; +} + +.top-1 { + top: 0.25rem; +} + +.top-2 { + top: 0.5rem; +} + +.top-3 { + top: 0.75rem; +} + +.top-4 { + top: 1rem; +} + +.top-40 { + top: 10rem; +} + +.z-10 { + z-index: 10; +} + +.z-50 { + z-index: 50; +} + +.col-span-full { + grid-column: 1 / -1; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.-mb-px { + margin-bottom: -1px; +} + +.-ml-px { + margin-left: -1px; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-10 { + margin-bottom: 2.5rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.mb-16 { + margin-bottom: 4rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-20 { + margin-bottom: 5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.ml-4 { + margin-left: 1rem; +} + +.ml-5 { + margin-left: 1.25rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.mr-4 { + margin-right: 1rem; +} + +.mt-0\.5 { + margin-top: 0.125rem; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mt-12 { + margin-top: 3rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-20 { + margin-top: 5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.line-clamp-1 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; +} + +.line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.aspect-square { + aspect-ratio: 1 / 1; +} + +.h-1 { + height: 0.25rem; +} + +.h-10 { + height: 2.5rem; +} + +.h-12 { + height: 3rem; +} + +.h-14 { + height: 3.5rem; +} + +.h-16 { + height: 4rem; +} + +.h-2 { + height: 0.5rem; +} + +.h-20 { + height: 5rem; +} + +.h-24 { + height: 6rem; +} + +.h-3 { + height: 0.75rem; +} + +.h-32 { + height: 8rem; +} + +.h-4 { + height: 1rem; +} + +.h-48 { + height: 12rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-64 { + height: 16rem; +} + +.h-7 { + height: 1.75rem; +} + +.h-8 { + height: 2rem; +} + +.h-80 { + height: 20rem; +} + +.h-96 { + height: 24rem; +} + +.h-auto { + height: auto; +} + +.h-full { + height: 100%; +} + +.max-h-96 { + max-height: 24rem; +} + +.max-h-\[90vh\] { + max-height: 90vh; +} + +.min-h-40 { + min-height: 10rem; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-0 { + width: 0px; +} + +.w-1\/2 { + width: 50%; +} + +.w-10 { + width: 2.5rem; +} + +.w-12 { + width: 3rem; +} + +.w-14 { + width: 3.5rem; +} + +.w-16 { + width: 4rem; +} + +.w-2 { + width: 0.5rem; +} + +.w-20 { + width: 5rem; +} + +.w-24 { + width: 6rem; +} + +.w-3 { + width: 0.75rem; +} + +.w-32 { + width: 8rem; +} + +.w-4 { + width: 1rem; +} + +.w-40 { + width: 10rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-64 { + width: 16rem; +} + +.w-8 { + width: 2rem; +} + +.w-80 { + width: 20rem; +} + +.w-auto { + width: auto; +} + +.w-full { + width: 100%; +} + +.min-w-0 { + min-width: 0px; +} + +.min-w-96 { + min-width: 24rem; +} + +.max-w-2xl { + max-width: 42rem; +} + +.max-w-3xl { + max-width: 48rem; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.max-w-7xl { + max-width: 80rem; +} + +.max-w-lg { + max-width: 32rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-none { + max-width: none; +} + +.max-w-sm { + max-width: 24rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.flex-grow { + flex-grow: 1; +} + +.-translate-x-1\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-x-full { + --tw-translate-x: 100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-y-4 { + --tw-translate-y: 1rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.scale-0 { + --tw-scale-x: 0; + --tw-scale-y: 0; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.scale-100 { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + animation-timing-function: cubic-bezier(0.8,0,1,1); + } + + 50% { + transform: none; + animation-timing-function: cubic-bezier(0,0,0.2,1); + } +} + +.animate-bounce { + animation: bounce 1s infinite; +} + +@keyframes pulse { + 50% { + opacity: .5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +.cursor-pointer { + cursor: pointer; +} + +.resize-none { + resize: none; +} + +.resize { + resize: both; +} + +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.items-start { + align-items: flex-start; +} + +.items-end { + align-items: flex-end; +} + +.items-center { + align-items: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-12 { + gap: 3rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-3 { + gap: 0.75rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.gap-8 { + gap: 2rem; +} + +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1.5rem * var(--tw-space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(2rem * var(--tw-space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.space-y-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-y-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); +} + +.space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity)); +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.whitespace-nowrap { + white-space: nowrap; +} + +.whitespace-pre-line { + white-space: pre-line; +} + +.whitespace-pre-wrap { + white-space: pre-wrap; +} + +.break-all { + word-break: break-all; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-2xl { + border-radius: 1rem; +} + +.rounded-3xl { + border-radius: 1.5rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.border { + border-width: 1px; +} + +.border-2 { + border-width: 2px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-b-2 { + border-bottom-width: 2px; +} + +.border-l-0 { + border-left-width: 0px; +} + +.border-t { + border-top-width: 1px; +} + +.border-t-2 { + border-top-width: 2px; +} + +.border-dashed { + border-style: dashed; +} + +.border-blue-200 { + --tw-border-opacity: 1; + border-color: rgb(191 219 254 / var(--tw-border-opacity)); +} + +.border-blue-500 { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.border-blue-600 { + --tw-border-opacity: 1; + border-color: rgb(37 99 235 / var(--tw-border-opacity)); +} + +.border-gray-100 { + --tw-border-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-border-opacity)); +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + +.border-gray-200\/50 { + border-color: rgb(229 231 235 / 0.5); +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-gray-800 { + --tw-border-opacity: 1; + border-color: rgb(31 41 55 / var(--tw-border-opacity)); +} + +.border-red-200 { + --tw-border-opacity: 1; + border-color: rgb(254 202 202 / var(--tw-border-opacity)); +} + +.border-transparent { + border-color: transparent; +} + +.border-white { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} + +.bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + +.bg-blue-400 { + --tw-bg-opacity: 1; + background-color: rgb(96 165 250 / var(--tw-bg-opacity)); +} + +.bg-blue-50 { + --tw-bg-opacity: 1; + background-color: rgb(239 246 255 / var(--tw-bg-opacity)); +} + +.bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); +} + +.bg-blue-600 { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity)); +} + +.bg-blue-700 { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.bg-emerald-100 { + --tw-bg-opacity: 1; + background-color: rgb(209 250 229 / var(--tw-bg-opacity)); +} + +.bg-emerald-500 { + --tw-bg-opacity: 1; + background-color: rgb(16 185 129 / var(--tw-bg-opacity)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-gray-300 { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); +} + +.bg-gray-600 { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +.bg-gray-900 { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.bg-green-100 { + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity)); +} + +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); +} + +.bg-green-600 { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.bg-indigo-500 { + --tw-bg-opacity: 1; + background-color: rgb(99 102 241 / var(--tw-bg-opacity)); +} + +.bg-orange-100 { + --tw-bg-opacity: 1; + background-color: rgb(255 237 213 / var(--tw-bg-opacity)); +} + +.bg-orange-500 { + --tw-bg-opacity: 1; + background-color: rgb(249 115 22 / var(--tw-bg-opacity)); +} + +.bg-orange-600 { + --tw-bg-opacity: 1; + background-color: rgb(234 88 12 / var(--tw-bg-opacity)); +} + +.bg-purple-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 232 255 / var(--tw-bg-opacity)); +} + +.bg-purple-500 { + --tw-bg-opacity: 1; + background-color: rgb(168 85 247 / var(--tw-bg-opacity)); +} + +.bg-purple-600 { + --tw-bg-opacity: 1; + background-color: rgb(147 51 234 / var(--tw-bg-opacity)); +} + +.bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); +} + +.bg-red-50 { + --tw-bg-opacity: 1; + background-color: rgb(254 242 242 / var(--tw-bg-opacity)); +} + +.bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); +} + +.bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-white\/20 { + background-color: rgb(255 255 255 / 0.2); +} + +.bg-white\/90 { + background-color: rgb(255 255 255 / 0.9); +} + +.bg-white\/95 { + background-color: rgb(255 255 255 / 0.95); +} + +.bg-yellow-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 249 195 / var(--tw-bg-opacity)); +} + +.bg-yellow-400 { + --tw-bg-opacity: 1; + background-color: rgb(250 204 21 / var(--tw-bg-opacity)); +} + +.bg-yellow-500 { + --tw-bg-opacity: 1; + background-color: rgb(234 179 8 / var(--tw-bg-opacity)); +} + +.bg-yellow-600 { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + +.bg-opacity-0 { + --tw-bg-opacity: 0; +} + +.bg-opacity-10 { + --tw-bg-opacity: 0.1; +} + +.bg-opacity-20 { + --tw-bg-opacity: 0.2; +} + +.bg-opacity-50 { + --tw-bg-opacity: 0.5; +} + +.bg-opacity-75 { + --tw-bg-opacity: 0.75; +} + +.bg-opacity-90 { + --tw-bg-opacity: 0.9; +} + +.bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); +} + +.bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); +} + +.bg-gradient-to-t { + background-image: linear-gradient(to top, var(--tw-gradient-stops)); +} + +.from-black\/20 { + --tw-gradient-from: rgb(0 0 0 / 0.2) var(--tw-gradient-from-position); + --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-black\/50 { + --tw-gradient-from: rgb(0 0 0 / 0.5) var(--tw-gradient-from-position); + --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-black\/70 { + --tw-gradient-from: rgb(0 0 0 / 0.7) var(--tw-gradient-from-position); + --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-100 { + --tw-gradient-from: #dbeafe var(--tw-gradient-from-position); + --tw-gradient-to: rgb(219 234 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-200 { + --tw-gradient-from: #bfdbfe var(--tw-gradient-from-position); + --tw-gradient-to: rgb(191 219 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-400 { + --tw-gradient-from: #60a5fa var(--tw-gradient-from-position); + --tw-gradient-to: rgb(96 165 250 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-50 { + --tw-gradient-from: #eff6ff var(--tw-gradient-from-position); + --tw-gradient-to: rgb(239 246 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-500 { + --tw-gradient-from: #3b82f6 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(59 130 246 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-600 { + --tw-gradient-from: #2563eb var(--tw-gradient-from-position); + --tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-600\/20 { + --tw-gradient-from: rgb(37 99 235 / 0.2) var(--tw-gradient-from-position); + --tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-900 { + --tw-gradient-from: #1e3a8a var(--tw-gradient-from-position); + --tw-gradient-to: rgb(30 58 138 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-emerald-50 { + --tw-gradient-from: #ecfdf5 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(236 253 245 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-gray-900 { + --tw-gradient-from: #111827 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-green-50 { + --tw-gradient-from: #f0fdf4 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(240 253 244 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-green-500 { + --tw-gradient-from: #22c55e var(--tw-gradient-from-position); + --tw-gradient-to: rgb(34 197 94 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-orange-50 { + --tw-gradient-from: #fff7ed var(--tw-gradient-from-position); + --tw-gradient-to: rgb(255 247 237 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-orange-500 { + --tw-gradient-from: #f97316 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(249 115 22 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-purple-50 { + --tw-gradient-from: #faf5ff var(--tw-gradient-from-position); + --tw-gradient-to: rgb(250 245 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-purple-600 { + --tw-gradient-from: #9333ea var(--tw-gradient-from-position); + --tw-gradient-to: rgb(147 51 234 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-red-50 { + --tw-gradient-from: #fef2f2 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(254 242 242 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-transparent { + --tw-gradient-from: transparent var(--tw-gradient-from-position); + --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-yellow-50 { + --tw-gradient-from: #fefce8 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(254 252 232 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.via-blue-900 { + --tw-gradient-to: rgb(30 58 138 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #1e3a8a var(--tw-gradient-via-position), var(--tw-gradient-to); +} + +.via-purple-600 { + --tw-gradient-to: rgb(147 51 234 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #9333ea var(--tw-gradient-via-position), var(--tw-gradient-to); +} + +.via-purple-900 { + --tw-gradient-to: rgb(88 28 135 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #581c87 var(--tw-gradient-via-position), var(--tw-gradient-to); +} + +.to-blue-100 { + --tw-gradient-to: #dbeafe var(--tw-gradient-to-position); +} + +.to-blue-800 { + --tw-gradient-to: #1e40af var(--tw-gradient-to-position); +} + +.to-current { + --tw-gradient-to: currentColor var(--tw-gradient-to-position); +} + +.to-emerald-100 { + --tw-gradient-to: #d1fae5 var(--tw-gradient-to-position); +} + +.to-green-100 { + --tw-gradient-to: #dcfce7 var(--tw-gradient-to-position); +} + +.to-indigo-100 { + --tw-gradient-to: #e0e7ff var(--tw-gradient-to-position); +} + +.to-indigo-900 { + --tw-gradient-to: #312e81 var(--tw-gradient-to-position); +} + +.to-orange-100 { + --tw-gradient-to: #ffedd5 var(--tw-gradient-to-position); +} + +.to-pink-600 { + --tw-gradient-to: #db2777 var(--tw-gradient-to-position); +} + +.to-purple-100 { + --tw-gradient-to: #f3e8ff var(--tw-gradient-to-position); +} + +.to-purple-400 { + --tw-gradient-to: #c084fc var(--tw-gradient-to-position); +} + +.to-purple-50 { + --tw-gradient-to: #faf5ff var(--tw-gradient-to-position); +} + +.to-purple-500 { + --tw-gradient-to: #a855f7 var(--tw-gradient-to-position); +} + +.to-purple-600 { + --tw-gradient-to: #9333ea var(--tw-gradient-to-position); +} + +.to-purple-600\/20 { + --tw-gradient-to: rgb(147 51 234 / 0.2) var(--tw-gradient-to-position); +} + +.to-purple-900 { + --tw-gradient-to: #581c87 var(--tw-gradient-to-position); +} + +.to-red-100 { + --tw-gradient-to: #fee2e2 var(--tw-gradient-to-position); +} + +.to-red-600 { + --tw-gradient-to: #dc2626 var(--tw-gradient-to-position); +} + +.to-teal-600 { + --tw-gradient-to: #0d9488 var(--tw-gradient-to-position); +} + +.to-transparent { + --tw-gradient-to: transparent var(--tw-gradient-to-position); +} + +.to-yellow-100 { + --tw-gradient-to: #fef9c3 var(--tw-gradient-to-position); +} + +.to-yellow-200 { + --tw-gradient-to: #fef08a var(--tw-gradient-to-position); +} + +.bg-clip-text { + -webkit-background-clip: text; + background-clip: text; +} + +.object-cover { + -o-object-fit: cover; + object-fit: cover; +} + +.p-12 { + padding: 3rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-2\.5 { + padding-left: 0.625rem; + padding-right: 0.625rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} + +.py-16 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-20 { + padding-top: 5rem; + padding-bottom: 5rem; +} + +.py-24 { + padding-top: 6rem; + padding-bottom: 6rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.pb-12 { + padding-bottom: 3rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.pb-3 { + padding-bottom: 0.75rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pr-10 { + padding-right: 2.5rem; +} + +.pr-3 { + padding-right: 0.75rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.pt-20 { + padding-top: 5rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.pt-8 { + padding-top: 2rem; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.font-mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.font-sans { + font-family: Ubuntu, system-ui, sans-serif; +} + +.text-2xl { + font-size: 1.125rem; + line-height: 1.5rem; +} + +.text-3xl { + font-size: 1.375rem; + line-height: 1.75rem; +} + +.text-4xl { + font-size: 1.625rem; + line-height: 2rem; +} + +.text-5xl { + font-size: 2rem; + line-height: 2.5rem; +} + +.text-6xl { + font-size: 2.5rem; + line-height: 3rem; +} + +.text-base { + font-size: 0.825rem; + line-height: 1.125rem; +} + +.text-lg { + font-size: 0.9rem; + line-height: 1.25rem; +} + +.text-sm { + font-size: 0.75rem; + line-height: 1rem; +} + +.text-xl { + font-size: 1rem; + line-height: 1.375rem; +} + +.text-xs { + font-size: 0.625rem; + line-height: 0.875rem; +} + +.font-bold { + font-weight: 700; +} + +.font-extrabold { + font-weight: 800; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.font-semibold { + font-weight: 600; +} + +.capitalize { + text-transform: capitalize; +} + +.italic { + font-style: italic; +} + +.leading-4 { + line-height: 1rem; +} + +.leading-6 { + line-height: 1.5rem; +} + +.leading-relaxed { + line-height: 1.625; +} + +.leading-tight { + line-height: 1.25; +} + +.text-blue-100 { + --tw-text-opacity: 1; + color: rgb(219 234 254 / var(--tw-text-opacity)); +} + +.text-blue-300 { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +.text-blue-700 { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.text-blue-800 { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity)); +} + +.text-blue-900 { + --tw-text-opacity: 1; + color: rgb(30 58 138 / var(--tw-text-opacity)); +} + +.text-emerald-600 { + --tw-text-opacity: 1; + color: rgb(5 150 105 / var(--tw-text-opacity)); +} + +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + +.text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.text-green-500 { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); +} + +.text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.text-green-700 { + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity)); +} + +.text-green-800 { + --tw-text-opacity: 1; + color: rgb(22 101 52 / var(--tw-text-opacity)); +} + +.text-indigo-600 { + --tw-text-opacity: 1; + color: rgb(79 70 229 / var(--tw-text-opacity)); +} + +.text-orange-500 { + --tw-text-opacity: 1; + color: rgb(249 115 22 / var(--tw-text-opacity)); +} + +.text-orange-600 { + --tw-text-opacity: 1; + color: rgb(234 88 12 / var(--tw-text-opacity)); +} + +.text-purple-500 { + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + +.text-purple-600 { + --tw-text-opacity: 1; + color: rgb(147 51 234 / var(--tw-text-opacity)); +} + +.text-red-300 { + --tw-text-opacity: 1; + color: rgb(252 165 165 / var(--tw-text-opacity)); +} + +.text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); +} + +.text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); +} + +.text-red-600 { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity)); +} + +.text-red-700 { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity)); +} + +.text-red-800 { + --tw-text-opacity: 1; + color: rgb(153 27 27 / var(--tw-text-opacity)); +} + +.text-transparent { + color: transparent; +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-yellow-300 { + --tw-text-opacity: 1; + color: rgb(253 224 71 / var(--tw-text-opacity)); +} + +.text-yellow-500 { + --tw-text-opacity: 1; + color: rgb(234 179 8 / var(--tw-text-opacity)); +} + +.text-yellow-600 { + --tw-text-opacity: 1; + color: rgb(202 138 4 / var(--tw-text-opacity)); +} + +.text-yellow-800 { + --tw-text-opacity: 1; + color: rgb(133 77 14 / var(--tw-text-opacity)); +} + +.text-opacity-75 { + --tw-text-opacity: 0.75; +} + +.text-opacity-90 { + --tw-text-opacity: 0.9; +} + +.underline { + text-decoration-line: underline; +} + +.placeholder-gray-500::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(107 114 128 / var(--tw-placeholder-opacity)); +} + +.placeholder-gray-500::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(107 114 128 / var(--tw-placeholder-opacity)); +} + +.opacity-0 { + opacity: 0; +} + +.opacity-100 { + opacity: 1; +} + +.opacity-30 { + opacity: 0.3; +} + +.opacity-50 { + opacity: 0.5; +} + +.opacity-70 { + opacity: 0.7; +} + +.opacity-90 { + opacity: 0.9; +} + +.mix-blend-multiply { + mix-blend-mode: multiply; +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-2xl { + --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); + --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-inner { + --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.outline { + outline-style: solid; +} + +.blur { + --tw-blur: blur(8px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.blur-xl { + --tw-blur: blur(24px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.backdrop-blur-lg { + --tw-backdrop-blur: blur(16px); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); +} + +.backdrop-blur-sm { + --tw-backdrop-blur: blur(4px); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-opacity { + transition-property: opacity; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-shadow { + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-transform { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.duration-500 { + transition-duration: 500ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Custom base styles */ + +/* Custom component styles */ + +/* Custom utility styles */ + +.file\:mr-4::file-selector-button { + margin-right: 1rem; +} + +.file\:rounded-full::file-selector-button { + border-radius: 9999px; +} + +.file\:border-0::file-selector-button { + border-width: 0px; +} + +.file\:bg-blue-50::file-selector-button { + --tw-bg-opacity: 1; + background-color: rgb(239 246 255 / var(--tw-bg-opacity)); +} + +.file\:px-4::file-selector-button { + padding-left: 1rem; + padding-right: 1rem; +} + +.file\:py-2::file-selector-button { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.file\:text-sm::file-selector-button { + font-size: 0.75rem; + line-height: 1rem; +} + +.file\:font-semibold::file-selector-button { + font-weight: 600; +} + +.file\:text-blue-700::file-selector-button { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.last\:border-b-0:last-child { + border-bottom-width: 0px; +} + +.hover\:-translate-y-1:hover { + --tw-translate-y: -0.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:-translate-y-2:hover { + --tw-translate-y: -0.5rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:scale-105:hover { + --tw-scale-x: 1.05; + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:scale-110:hover { + --tw-scale-x: 1.1; + --tw-scale-y: 1.1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:border-blue-500:hover { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.hover\:border-gray-300:hover { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.hover\:bg-blue-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(239 246 255 / var(--tw-bg-opacity)); +} + +.hover\:bg-blue-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); +} + +.hover\:bg-blue-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity)); +} + +.hover\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.hover\:bg-blue-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-300:hover { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.hover\:bg-green-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity)); +} + +.hover\:bg-green-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.hover\:bg-green-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); +} + +.hover\:bg-indigo-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(238 242 255 / var(--tw-bg-opacity)); +} + +.hover\:bg-purple-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(126 34 206 / var(--tw-bg-opacity)); +} + +.hover\:bg-red-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 242 242 / var(--tw-bg-opacity)); +} + +.hover\:bg-red-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} + +.hover\:bg-red-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); +} + +.hover\:bg-white:hover { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.hover\:bg-yellow-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 252 232 / var(--tw-bg-opacity)); +} + +.hover\:from-blue-600:hover { + --tw-gradient-from: #2563eb var(--tw-gradient-from-position); + --tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.hover\:from-blue-700:hover { + --tw-gradient-from: #1d4ed8 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(29 78 216 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.hover\:to-purple-700:hover { + --tw-gradient-to: #7e22ce var(--tw-gradient-to-position); +} + +.hover\:text-blue-300:hover { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); +} + +.hover\:text-blue-500:hover { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.hover\:text-blue-600:hover { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +.hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.hover\:text-blue-800:hover { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity)); +} + +.hover\:text-blue-900:hover { + --tw-text-opacity: 1; + color: rgb(30 58 138 / var(--tw-text-opacity)); +} + +.hover\:text-gray-600:hover { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.hover\:text-gray-700:hover { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.hover\:text-green-500:hover { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); +} + +.hover\:text-green-600:hover { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.hover\:text-indigo-600:hover { + --tw-text-opacity: 1; + color: rgb(79 70 229 / var(--tw-text-opacity)); +} + +.hover\:text-indigo-900:hover { + --tw-text-opacity: 1; + color: rgb(49 46 129 / var(--tw-text-opacity)); +} + +.hover\:text-orange-500:hover { + --tw-text-opacity: 1; + color: rgb(249 115 22 / var(--tw-text-opacity)); +} + +.hover\:text-purple-500:hover { + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + +.hover\:text-red-600:hover { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity)); +} + +.hover\:text-red-800:hover { + --tw-text-opacity: 1; + color: rgb(153 27 27 / var(--tw-text-opacity)); +} + +.hover\:text-red-900:hover { + --tw-text-opacity: 1; + color: rgb(127 29 29 / var(--tw-text-opacity)); +} + +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover\:text-yellow-600:hover { + --tw-text-opacity: 1; + color: rgb(202 138 4 / var(--tw-text-opacity)); +} + +.hover\:underline:hover { + text-decoration-line: underline; +} + +.hover\:opacity-100:hover { + opacity: 1; +} + +.hover\:shadow-2xl:hover { + --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); + --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.hover\:shadow-lg:hover { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.hover\:shadow-md:hover { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.hover\:shadow-xl:hover { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.hover\:file\:bg-blue-100::file-selector-button:hover { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + +.focus\:z-10:focus { + z-index: 10; +} + +.focus\:border-blue-500:focus { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.focus\:border-indigo-500:focus { + --tw-border-opacity: 1; + border-color: rgb(99 102 241 / var(--tw-border-opacity)); +} + +.focus\:border-transparent:focus { + border-color: transparent; +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-1:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); +} + +.focus\:ring-gray-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity)); +} + +.focus\:ring-indigo-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity)); +} + +.focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} + +.disabled\:cursor-not-allowed:disabled { + cursor: not-allowed; +} + +.disabled\:opacity-50:disabled { + opacity: 0.5; +} + +.disabled\:hover\:scale-100:hover:disabled { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.group:hover .group-hover\:visible { + visibility: visible; +} + +.group:hover .group-hover\:translate-y-0 { + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.group:hover .group-hover\:scale-100 { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.group:hover .group-hover\:scale-110 { + --tw-scale-x: 1.1; + --tw-scale-y: 1.1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.group:hover .group-hover\:border-blue-500 { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:border-emerald-500 { + --tw-border-opacity: 1; + border-color: rgb(16 185 129 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:border-green-500 { + --tw-border-opacity: 1; + border-color: rgb(34 197 94 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:border-orange-500 { + --tw-border-opacity: 1; + border-color: rgb(249 115 22 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:border-purple-500 { + --tw-border-opacity: 1; + border-color: rgb(168 85 247 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:border-yellow-500 { + --tw-border-opacity: 1; + border-color: rgb(234 179 8 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:bg-opacity-25 { + --tw-bg-opacity: 0.25; +} + +.group:hover .group-hover\:text-blue-400 { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); +} + +.group:hover .group-hover\:text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +.group:hover .group-hover\:opacity-100 { + opacity: 1; +} + +@media (min-width: 640px) { + .sm\:col-span-2 { + grid-column: span 2 / span 2; + } + + .sm\:hidden { + display: none; + } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:rounded-md { + border-radius: 0.375rem; + } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .sm\:text-sm { + font-size: 0.75rem; + line-height: 1rem; + } +} + +@media (min-width: 768px) { + .md\:col-span-2 { + grid-column: span 2 / span 2; + } + + .md\:mt-0 { + margin-top: 0px; + } + + .md\:flex { + display: flex; + } + + .md\:hidden { + display: none; + } + + .md\:h-80 { + height: 20rem; + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:items-center { + align-items: center; + } + + .md\:justify-between { + justify-content: space-between; + } + + .md\:space-y-0 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0px * var(--tw-space-y-reverse)); + } + + .md\:p-12 { + padding: 3rem; + } + + .md\:text-2xl { + font-size: 1.125rem; + line-height: 1.5rem; + } + + .md\:text-3xl { + font-size: 1.375rem; + line-height: 1.75rem; + } + + .md\:text-4xl { + font-size: 1.625rem; + line-height: 2rem; + } + + .md\:text-5xl { + font-size: 2rem; + line-height: 2.5rem; + } + + .md\:text-6xl { + font-size: 2.5rem; + line-height: 3rem; + } +} + +@media (min-width: 1024px) { + .lg\:order-first { + order: -9999; + } + + .lg\:col-span-1 { + grid-column: span 1 / span 1; + } + + .lg\:col-span-2 { + grid-column: span 2 / span 2; + } + + .lg\:w-80 { + width: 20rem; + } + + .lg\:w-auto { + width: auto; + } + + .lg\:flex-1 { + flex: 1 1 0%; + } + + .lg\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .lg\:flex-row { + flex-direction: row; + } + + .lg\:items-center { + align-items: center; + } + + .lg\:justify-end { + justify-content: flex-end; + } + + .lg\:space-x-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1.5rem * var(--tw-space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); + } + + .lg\:space-y-0 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0px * var(--tw-space-y-reverse)); + } + + .lg\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .lg\:text-left { + text-align: left; + } + + .lg\:text-3xl { + font-size: 1.375rem; + line-height: 1.75rem; + } +} + +@media (min-width: 1280px) { + .xl\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } +} + +@media (prefers-color-scheme: dark) { + .dark\:border-blue-400 { + --tw-border-opacity: 1; + border-color: rgb(96 165 250 / var(--tw-border-opacity)); + } + + .dark\:border-blue-700 { + --tw-border-opacity: 1; + border-color: rgb(29 78 216 / var(--tw-border-opacity)); + } + + .dark\:border-gray-500 { + --tw-border-opacity: 1; + border-color: rgb(107 114 128 / var(--tw-border-opacity)); + } + + .dark\:border-gray-600 { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); + } + + .dark\:border-gray-600\/50 { + border-color: rgb(75 85 99 / 0.5); + } + + .dark\:border-gray-700 { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); + } + + .dark\:bg-blue-400 { + --tw-bg-opacity: 1; + background-color: rgb(96 165 250 / var(--tw-bg-opacity)); + } + + .dark\:bg-blue-900 { + --tw-bg-opacity: 1; + background-color: rgb(30 58 138 / var(--tw-bg-opacity)); + } + + .dark\:bg-emerald-900 { + --tw-bg-opacity: 1; + background-color: rgb(6 78 59 / var(--tw-bg-opacity)); + } + + .dark\:bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); + } + + .dark\:bg-gray-600 { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); + } + + .dark\:bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); + } + + .dark\:bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); + } + + .dark\:bg-gray-800\/90 { + background-color: rgb(31 41 55 / 0.9); + } + + .dark\:bg-gray-800\/95 { + background-color: rgb(31 41 55 / 0.95); + } + + .dark\:bg-gray-900 { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); + } + + .dark\:bg-gray-950 { + --tw-bg-opacity: 1; + background-color: rgb(3 7 18 / var(--tw-bg-opacity)); + } + + .dark\:bg-green-900 { + --tw-bg-opacity: 1; + background-color: rgb(20 83 45 / var(--tw-bg-opacity)); + } + + .dark\:bg-indigo-400 { + --tw-bg-opacity: 1; + background-color: rgb(129 140 248 / var(--tw-bg-opacity)); + } + + .dark\:bg-orange-900 { + --tw-bg-opacity: 1; + background-color: rgb(124 45 18 / var(--tw-bg-opacity)); + } + + .dark\:bg-purple-400 { + --tw-bg-opacity: 1; + background-color: rgb(192 132 252 / var(--tw-bg-opacity)); + } + + .dark\:bg-purple-500 { + --tw-bg-opacity: 1; + background-color: rgb(168 85 247 / var(--tw-bg-opacity)); + } + + .dark\:bg-purple-900 { + --tw-bg-opacity: 1; + background-color: rgb(88 28 135 / var(--tw-bg-opacity)); + } + + .dark\:bg-red-900 { + --tw-bg-opacity: 1; + background-color: rgb(127 29 29 / var(--tw-bg-opacity)); + } + + .dark\:bg-yellow-900 { + --tw-bg-opacity: 1; + background-color: rgb(113 63 18 / var(--tw-bg-opacity)); + } + + .dark\:from-blue-900 { + --tw-gradient-from: #1e3a8a var(--tw-gradient-from-position); + --tw-gradient-to: rgb(30 58 138 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-emerald-900 { + --tw-gradient-from: #064e3b var(--tw-gradient-from-position); + --tw-gradient-to: rgb(6 78 59 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-gray-700 { + --tw-gradient-from: #374151 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(55 65 81 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-gray-900 { + --tw-gradient-from: #111827 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-green-900 { + --tw-gradient-from: #14532d var(--tw-gradient-from-position); + --tw-gradient-to: rgb(20 83 45 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-orange-900 { + --tw-gradient-from: #7c2d12 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(124 45 18 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-purple-900 { + --tw-gradient-from: #581c87 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(88 28 135 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-red-900 { + --tw-gradient-from: #7f1d1d var(--tw-gradient-from-position); + --tw-gradient-to: rgb(127 29 29 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:from-yellow-900 { + --tw-gradient-from: #713f12 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(113 63 18 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + + .dark\:via-blue-900 { + --tw-gradient-to: rgb(30 58 138 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #1e3a8a var(--tw-gradient-via-position), var(--tw-gradient-to); + } + + .dark\:to-blue-800 { + --tw-gradient-to: #1e40af var(--tw-gradient-to-position); + } + + .dark\:to-emerald-800 { + --tw-gradient-to: #065f46 var(--tw-gradient-to-position); + } + + .dark\:to-gray-600 { + --tw-gradient-to: #4b5563 var(--tw-gradient-to-position); + } + + .dark\:to-gray-800 { + --tw-gradient-to: #1f2937 var(--tw-gradient-to-position); + } + + .dark\:to-green-800 { + --tw-gradient-to: #166534 var(--tw-gradient-to-position); + } + + .dark\:to-orange-800 { + --tw-gradient-to: #9a3412 var(--tw-gradient-to-position); + } + + .dark\:to-purple-800 { + --tw-gradient-to: #6b21a8 var(--tw-gradient-to-position); + } + + .dark\:to-purple-900 { + --tw-gradient-to: #581c87 var(--tw-gradient-to-position); + } + + .dark\:to-red-800 { + --tw-gradient-to: #991b1b var(--tw-gradient-to-position); + } + + .dark\:to-yellow-800 { + --tw-gradient-to: #854d0e var(--tw-gradient-to-position); + } + + .dark\:text-blue-300 { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); + } + + .dark\:text-blue-400 { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); + } + + .dark\:text-emerald-400 { + --tw-text-opacity: 1; + color: rgb(52 211 153 / var(--tw-text-opacity)); + } + + .dark\:text-gray-100 { + --tw-text-opacity: 1; + color: rgb(243 244 246 / var(--tw-text-opacity)); + } + + .dark\:text-gray-200 { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); + } + + .dark\:text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); + } + + .dark\:text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); + } + + .dark\:text-green-400 { + --tw-text-opacity: 1; + color: rgb(74 222 128 / var(--tw-text-opacity)); + } + + .dark\:text-orange-400 { + --tw-text-opacity: 1; + color: rgb(251 146 60 / var(--tw-text-opacity)); + } + + .dark\:text-purple-400 { + --tw-text-opacity: 1; + color: rgb(192 132 252 / var(--tw-text-opacity)); + } + + .dark\:text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); + } + + .dark\:text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + } + + .dark\:text-yellow-400 { + --tw-text-opacity: 1; + color: rgb(250 204 21 / var(--tw-text-opacity)); + } + + .dark\:opacity-70 { + opacity: 0.7; + } + + .dark\:hover\:bg-blue-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(96 165 250 / var(--tw-bg-opacity)); + } + + .dark\:hover\:bg-gray-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); + } + + .dark\:hover\:bg-gray-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); + } + + .dark\:hover\:bg-purple-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(192 132 252 / var(--tw-bg-opacity)); + } + + .dark\:hover\:text-blue-300:hover { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); + } + + .dark\:hover\:text-gray-200:hover { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); + } + + .dark\:hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); + } + + .group:hover .dark\:group-hover\:text-blue-400 { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); + } +} \ No newline at end of file diff --git a/public/css/theme-toggle.css b/public/css/theme-toggle.css new file mode 100644 index 0000000..77d6edd --- /dev/null +++ b/public/css/theme-toggle.css @@ -0,0 +1,103 @@ +/* Animated Theme Toggle Styles */ +.theme-toggle-slider { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + background-color 0.3s ease; +} + +.theme-sun-icon, +.theme-moon-icon { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Initial states - Light theme */ +.theme-sun-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +.theme-moon-icon { + transform: rotate(-180deg) scale(0); + opacity: 0; +} + +/* Dark theme states */ +.dark .theme-sun-icon { + transform: rotate(180deg) scale(0); + opacity: 0; +} + +.dark .theme-moon-icon { + transform: rotate(0deg) scale(1); + opacity: 1; +} + +/* Toggle background enhancement with perfect centering */ +.theme-toggle-bg { + background: linear-gradient(135deg, #e0f2fe 0%, #fff3e0 100%); + border: 2px solid #e5e7eb; + transition: all 0.3s ease; + position: relative; + display: flex; + align-items: center; +} + +.dark .theme-toggle-bg { + background: linear-gradient(135deg, #374151 0%, #4b5563 100%); + border-color: #6b7280; +} + +/* Enhanced slider styles with perfect centering */ +.theme-toggle-slider { + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + /* Позволяем Tailwind CSS управлять позиционированием */ +} + +.dark .theme-toggle-slider { + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border-color: rgba(0, 0, 0, 0.2); +} + +/* Hover effects */ +label:hover .theme-toggle-slider { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Focus styles for accessibility */ +input[type="checkbox"]:focus + label > div { + box-shadow: 0 0 0 2px #3b82f6, 0 0 0 4px rgba(59, 130, 246, 0.1); +} + +/* Animation keyframes for icon rotation */ +@keyframes iconSpinIn { + from { + transform: rotate(-180deg) scale(0); + opacity: 0; + } + to { + transform: rotate(0deg) scale(1); + opacity: 1; + } +} + +@keyframes iconSpinOut { + from { + transform: rotate(0deg) scale(1); + opacity: 1; + } + to { + transform: rotate(180deg) scale(0); + opacity: 0; + } +} + +/* Animation states */ +.icon-animate-in { + animation: iconSpinIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.icon-animate-out { + animation: iconSpinOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} \ No newline at end of file diff --git a/public/js/calculator-modern.js b/public/js/calculator-modern.js new file mode 100644 index 0000000..dee3df8 --- /dev/null +++ b/public/js/calculator-modern.js @@ -0,0 +1,676 @@ +/** + * Modern Calculator - UX-polished service cost calculator + * Production-ready calculator with a11y, dark mode, localStorage, live price updates + */ + +class ModernCalculator { + constructor() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.getStoredTheme() + }; + + this.services = { + web: { key: 'web', basePrice: 500000, name: 'Веб-разработка' }, + mobile: { key: 'mobile', basePrice: 800000, name: 'Мобильные приложения' }, + design: { key: 'design', basePrice: 300000, name: 'UI/UX Дизайн' }, + marketing: { key: 'marketing', basePrice: 200000, name: 'Цифровой маркетинг' } + }; + + this.complexity = { + simple: { key: 'simple', multiplier: 1, name: 'Простой' }, + medium: { key: 'medium', multiplier: 1.5, name: 'Средний' }, + complex: { key: 'complex', multiplier: 2.5, name: 'Сложный' } + }; + + this.timeline = { + extended: { key: 'extended', multiplier: 0.8, name: 'Увеличенные сроки' }, + standard: { key: 'standard', multiplier: 1, name: 'Стандартные сроки' }, + rush: { key: 'rush', multiplier: 1.5, name: 'Срочно' } + }; + + this.promoCodes = { + 'HELLO10': 0.9, + 'FRIENDS5': 0.95 + }; + + this.storageKey = 'calculator_draft'; + + this.init(); + } + + init() { + this.loadFromStorage(); + this.setupEventListeners(); + this.updateUI(); + this.applyTheme(); + this.setupKeyboardNavigation(); + + // Show sticky price display on page load + setTimeout(() => { + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + }, 500); + } + + // Storage management + loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const savedState = JSON.parse(saved); + this.state = { ...this.state, ...savedState }; + } + } catch (e) { + console.warn('Failed to load calculator state from localStorage'); + } + } + + saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save calculator state to localStorage'); + } + } + + getStoredTheme() { + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + // Theme management + applyTheme() { + document.documentElement.classList.toggle('dark', this.state.darkMode); + localStorage.setItem('theme', this.state.darkMode ? 'dark' : 'light'); + + const toggle = document.getElementById('darkModeToggle'); + if (toggle) toggle.checked = this.state.darkMode; + } + + // Price calculation + calculatePrice() { + if (!this.state.selectedService) return 0; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + + return Math.round(basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier); + } + + formatPrice(amount) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount); + } + + // Translation helper + t(key) { + const translations = { + 'calculator.result.estimated_price': 'Расчетная цена', + 'calculator.complexity.simple': 'Простой', + 'calculator.complexity.medium': 'Средний', + 'calculator.complexity.complex': 'Сложный', + 'calculator.timeline.extended': 'Увеличенные сроки', + 'calculator.timeline.standard': 'Стандартные сроки', + 'calculator.timeline.rush': 'Срочно', + 'calculator.next_step': 'Далее', + 'calculator.calculate': 'Рассчитать', + 'services.web.title': 'Веб-разработка', + 'services.mobile.title': 'Мобильные приложения', + 'services.design.title': 'UI/UX Дизайн', + 'services.marketing.title': 'Цифровой маркетинг' + }; + + if (window.calculatorTranslations && window.calculatorTranslations[key]) { + return window.calculatorTranslations[key]; + } + + return translations[key] || key; + } + + // UI Updates + updatePriceDisplay() { + const price = this.calculatePrice(); + const formattedPrice = this.formatPrice(price); + + // Update main price displays + const priceElements = ['currentPrice', 'finalPrice']; + priceElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + element.textContent = formattedPrice; + element.setAttribute('aria-live', 'polite'); + } + }); + + // Update detailed breakdown + this.updatePriceBreakdown(); + + // Show sticky price display + const stickyContainer = document.getElementById('stickyPriceContainer'); + if (stickyContainer) { + stickyContainer.style.display = 'block'; + } + } + + updatePriceBreakdown() { + // Service selection + const selectedServiceDiv = document.getElementById('selectedService'); + const serviceName = document.getElementById('serviceName'); + const servicePrice = document.getElementById('servicePrice'); + + if (this.state.selectedService && selectedServiceDiv && serviceName && servicePrice) { + const service = this.services[this.state.selectedService]; + serviceName.textContent = service.name; + servicePrice.textContent = this.formatPrice(service.basePrice); + selectedServiceDiv.classList.remove('hidden'); + } else if (selectedServiceDiv) { + selectedServiceDiv.classList.add('hidden'); + } + + // Complexity + const selectedComplexityDiv = document.getElementById('selectedComplexity'); + const complexityName = document.getElementById('complexityName'); + const complexityMultiplier = document.getElementById('complexityMultiplier'); + + if (this.state.selectedComplexity && selectedComplexityDiv && complexityName && complexityMultiplier) { + const complexity = this.complexity[this.state.selectedComplexity]; + complexityName.textContent = complexity.name; + complexityMultiplier.textContent = complexity.multiplier; + selectedComplexityDiv.classList.remove('hidden'); + } else if (selectedComplexityDiv) { + selectedComplexityDiv.classList.add('hidden'); + } + + // Timeline + const selectedTimelineDiv = document.getElementById('selectedTimeline'); + const timelineName = document.getElementById('timelineName'); + const timelineMultiplier = document.getElementById('timelineMultiplier'); + + if (this.state.selectedTimeline && selectedTimelineDiv && timelineName && timelineMultiplier) { + const timeline = this.timeline[this.state.selectedTimeline]; + timelineName.textContent = timeline.name; + timelineMultiplier.textContent = timeline.multiplier; + selectedTimelineDiv.classList.remove('hidden'); + } else if (selectedTimelineDiv) { + selectedTimelineDiv.classList.add('hidden'); + } + + // Promo code + const appliedPromoDiv = document.getElementById('appliedPromo'); + const promoCode = document.getElementById('promoCode'); + const promoDiscount = document.getElementById('promoDiscount'); + + if (this.state.promoCode && this.promoCodes[this.state.promoCode.toUpperCase()] && appliedPromoDiv && promoCode && promoDiscount) { + const discount = this.promoCodes[this.state.promoCode.toUpperCase()]; + const discountPercent = Math.round((1 - discount) * 100); + promoCode.textContent = this.state.promoCode.toUpperCase(); + promoDiscount.textContent = `-${discountPercent}%`; + appliedPromoDiv.classList.remove('hidden'); + } else if (appliedPromoDiv) { + appliedPromoDiv.classList.add('hidden'); + } + + // Final calculation + const finalCalculation = document.getElementById('finalCalculation'); + if (finalCalculation) { + const price = this.calculatePrice(); + finalCalculation.textContent = this.formatPrice(price); + } + } + + updateStepIndicators() { + for (let i = 0; i < 3; i++) { + const indicator = document.getElementById(`step-indicator-${i}`); + const checkIcon = document.getElementById(`check-${i}`); + const numberSpan = document.getElementById(`number-${i}`); + const progressLine = document.getElementById(`progress-line-${i}`); + + if (!indicator) continue; + + // Reset classes + indicator.className = 'w-12 h-12 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300'; + + if (i < this.state.step) { + // Completed step + indicator.classList.add('bg-green-500', 'text-white', 'shadow-lg'); + if (checkIcon) { + checkIcon.classList.remove('hidden'); + numberSpan.classList.add('hidden'); + } + if (progressLine) progressLine.style.width = '100%'; + } else if (i === this.state.step) { + // Current step + indicator.classList.add('bg-blue-600', 'text-white', 'shadow-lg'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } else { + // Future step + indicator.classList.add('bg-gray-300', 'dark:bg-gray-600', 'text-gray-600', 'dark:text-gray-400'); + if (checkIcon) checkIcon.classList.add('hidden'); + if (numberSpan) numberSpan.classList.remove('hidden'); + if (progressLine) progressLine.style.width = '0%'; + } + } + + // Update overall progress + const overallProgress = document.getElementById('overallProgress'); + if (overallProgress) { + const progressPercentage = ((this.state.step + 1) / 3) * 100; + overallProgress.style.width = `${progressPercentage}%`; + } + } + + updateStepContent() { + // Hide all steps + document.querySelectorAll('.step-content').forEach(step => { + step.classList.add('hidden'); + }); + + // Show current step + const currentStep = document.getElementById(`step-${this.state.step + 1}`); + if (currentStep) { + currentStep.classList.remove('hidden'); + } + + // Update navigation buttons + this.updateNavigationButtons(); + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + const nextBtnText = document.getElementById('nextBtnText'); + + if (prevBtn) { + if (this.state.step > 0) { + prevBtn.classList.remove('hidden'); + } else { + prevBtn.classList.add('hidden'); + } + } + + if (nextBtn && nextBtnText) { + if (this.state.step < 2) { + nextBtn.classList.remove('hidden'); + const canProceed = this.canProceedToNextStep(); + nextBtn.disabled = !canProceed; + + if (this.state.step === 1) { + nextBtnText.textContent = this.t('calculator.calculate'); + } else { + nextBtnText.textContent = this.t('calculator.next_step'); + } + } else { + nextBtn.classList.add('hidden'); + } + } + } + + canProceedToNextStep() { + switch (this.state.step) { + case 0: return !!this.state.selectedService; + case 1: return !!this.state.selectedComplexity && !!this.state.selectedTimeline; + case 2: return true; + default: return false; + } + } + + updateSelectionUI() { + // Update service selection + document.querySelectorAll('.service-card').forEach(card => { + const service = card.dataset.service; + const indicator = card.querySelector('.service-indicator'); + + if (service === this.state.selectedService) { + card.classList.add('selected'); + if (indicator) indicator.classList.add('scale-100'); + card.setAttribute('aria-pressed', 'true'); + } else { + card.classList.remove('selected'); + if (indicator) indicator.classList.remove('scale-100'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + const complexity = card.dataset.complexity; + const cardDiv = card.querySelector('div'); + + if (complexity === this.state.selectedComplexity) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + + // Update timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + const timeline = card.dataset.timeline; + const cardDiv = card.querySelector('div'); + + if (timeline === this.state.selectedTimeline) { + cardDiv.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.remove('border-transparent'); + card.setAttribute('aria-pressed', 'true'); + } else { + cardDiv.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); + cardDiv.classList.add('border-transparent'); + card.setAttribute('aria-pressed', 'false'); + } + }); + } + + updatePriceBreakdown() { + const breakdown = document.getElementById('priceBreakdown'); + if (!breakdown || this.state.step !== 2) return; + + breakdown.innerHTML = ''; + + if (!this.state.selectedService) return; + + const basePrice = this.services[this.state.selectedService].basePrice; + const complexityMultiplier = this.state.selectedComplexity ? + this.complexity[this.state.selectedComplexity].multiplier : 1; + const timelineMultiplier = this.state.selectedTimeline ? + this.timeline[this.state.selectedTimeline].multiplier : 1; + const promoMultiplier = this.promoCodes[this.state.promoCode.toUpperCase()] || 1; + const appliedPromo = this.promoCodes[this.state.promoCode.toUpperCase()] ? + this.state.promoCode.toUpperCase() : ''; + + // Base price + breakdown.appendChild(this.createPriceLineElement( + 'Базовая стоимость', + basePrice + )); + + // Complexity adjustment + if (complexityMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сложность', + basePrice * complexityMultiplier, + `×${complexityMultiplier}` + )); + } + + // Timeline adjustment + if (timelineMultiplier !== 1) { + breakdown.appendChild(this.createPriceLineElement( + 'Сроки', + basePrice * complexityMultiplier * timelineMultiplier, + `×${timelineMultiplier}` + )); + } + + // Promo code discount + if (appliedPromo) { + breakdown.appendChild(this.createPriceLineElement( + 'Промокод', + basePrice * complexityMultiplier * timelineMultiplier * promoMultiplier, + appliedPromo + )); + } + } + + createPriceLineElement(label, amount, badge = null) { + const div = document.createElement('div'); + div.className = 'flex justify-between items-center py-3 border-b border-gray-100 dark:border-gray-700 last:border-b-0'; + + div.innerHTML = ` + + ${label} + ${badge ? `${badge}` : ''} + + ${this.formatPrice(amount)} + `; + + return div; + } + + updateUI() { + this.updateStepIndicators(); + this.updateStepContent(); + this.updateSelectionUI(); + this.updatePriceDisplay(); + this.updatePriceBreakdown(); + this.saveToStorage(); + } + + // Event handlers + setupEventListeners() { + // Reset button + const resetButton = document.getElementById('resetButton'); + if (resetButton) { + resetButton.addEventListener('click', () => { + this.resetCalculator(); + }); + } + + // Dark mode toggle + const darkModeToggle = document.getElementById('darkModeToggle'); + if (darkModeToggle) { + darkModeToggle.addEventListener('change', () => { + this.state.darkMode = darkModeToggle.checked; + this.applyTheme(); + this.saveToStorage(); + }); + } + + // Service selection + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + const service = card.dataset.service; + this.state.selectedService = service; + this.updateUI(); + + // Announce selection for screen readers + this.announceForScreenReader( + `Выбрана услуга ${this.services[service].name}` + ); + }); + }); + + // Complexity selection + document.querySelectorAll('.complexity-card').forEach(card => { + card.addEventListener('click', () => { + const complexity = card.dataset.complexity; + this.state.selectedComplexity = complexity; + this.updateUI(); + }); + }); + + // Timeline selection + document.querySelectorAll('.timeline-card').forEach(card => { + card.addEventListener('click', () => { + const timeline = card.dataset.timeline; + this.state.selectedTimeline = timeline; + this.updateUI(); + }); + }); + + // Navigation buttons + const prevBtn = document.getElementById('prevBtn'); + const nextBtn = document.getElementById('nextBtn'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => { + if (this.state.step > 0) { + this.state.step--; + this.updateUI(); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => { + if (this.state.step < 2 && this.canProceedToNextStep()) { + this.state.step++; + this.updateUI(); + } + }); + } + + // Promo code + const applyPromoBtn = document.getElementById('applyPromo'); + if (applyPromoBtn) { + applyPromoBtn.addEventListener('click', this.applyPromoCode.bind(this)); + } + + const promoInput = document.getElementById('promoCode'); + if (promoInput) { + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.applyPromoCode(); + } + }); + } + + // Final actions + const getQuoteBtn = document.getElementById('getQuoteBtn'); + if (getQuoteBtn) { + getQuoteBtn.addEventListener('click', this.submitQuote.bind(this)); + } + + const recalculateBtn = document.getElementById('recalculateBtn'); + if (recalculateBtn) { + recalculateBtn.addEventListener('click', this.resetCalculator.bind(this)); + } + } + + setupKeyboardNavigation() { + // Add keyboard support for card selections + document.querySelectorAll('.service-card, .complexity-card, .timeline-card').forEach(card => { + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'button'); + + card.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + card.click(); + } + }); + }); + } + + applyPromoCode() { + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (!promoInput || !promoStatus) return; + + const code = promoInput.value.trim().toUpperCase(); + + if (this.promoCodes[code]) { + this.state.promoCode = code; + promoStatus.textContent = 'Промокод применен'; + promoStatus.className = 'mt-2 text-sm text-green-600 dark:text-green-400'; + this.updateUI(); + } else if (code) { + promoStatus.textContent = 'Неверный промокод'; + promoStatus.className = 'mt-2 text-sm text-red-600 dark:text-red-400'; + } else { + this.state.promoCode = ''; + promoStatus.textContent = ''; + this.updateUI(); + } + } + + submitQuote() { + const finalPrice = this.calculatePrice(); + + // In a real application, this would send data to the server + alert(`Заявка отправлена!\n\nИтоговая стоимость: ${this.formatPrice(finalPrice)}\n\nМы свяжемся с вами в ближайшее время.`); + + // Clear the form + this.resetCalculator(); + } + + resetCalculator() { + this.state = { + step: 0, + selectedService: null, + selectedComplexity: null, + selectedTimeline: null, + promoCode: '', + darkMode: this.state.darkMode + }; + + // Clear promo code input and status + const promoInput = document.getElementById('promoCode'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoInput) promoInput.value = ''; + if (promoStatus) promoStatus.textContent = ''; + + this.updateUI(); + } + + announceForScreenReader(message) { + const announcement = document.createElement('div'); + announcement.setAttribute('aria-live', 'polite'); + announcement.setAttribute('aria-atomic', 'true'); + announcement.className = 'sr-only'; + announcement.textContent = message; + + document.body.appendChild(announcement); + + setTimeout(() => { + document.body.removeChild(announcement); + }, 1000); + } +} + +// Initialize calculator when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + new ModernCalculator(); +}); + +// Add CSS classes for screen reader only content +const style = document.createElement('style'); +style.textContent = ` + .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; + } + + .service-card.selected .service-indicator { + transform: scale(1); + } + + .service-card:focus, + .complexity-card:focus, + .timeline-card:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } +`; + +document.head.appendChild(style); \ No newline at end of file diff --git a/public/js/calculator.js b/public/js/calculator.js deleted file mode 100644 index 8bbbfcb..0000000 --- a/public/js/calculator.js +++ /dev/null @@ -1,384 +0,0 @@ -// 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 += `
${servicesLabel}:
`; - this.selectedServices.forEach(service => { - summaryHTML += `
- - ${this.getServiceName(service.service)} - ₩${service.price.toLocaleString()} -
`; - }); - - // Complexity - if (this.selectedComplexity) { - summaryHTML += `
- - ${complexityLabel}: - ${this.getComplexityName(this.selectedComplexity.value)} - ×${this.selectedComplexity.multiplier} -
`; - } - - // Timeline - if (this.selectedTimeline) { - summaryHTML += `
- - ${timelineLabel}: - ${this.getTimelineName(this.selectedTimeline.value)} - ×${this.selectedTimeline.multiplier} -
`; - } - - // 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(); - } -}); \ No newline at end of file diff --git a/public/js/debug-calculator.js b/public/js/debug-calculator.js new file mode 100644 index 0000000..6ebae81 --- /dev/null +++ b/public/js/debug-calculator.js @@ -0,0 +1,52 @@ +// Отладочная версия калькулятора +console.log('Debug calculator loaded'); + +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM loaded'); + + // Проверим наличие элементов + const priceDisplay = document.getElementById('priceDisplay'); + const mobilePriceDisplay = document.getElementById('mobilePriceDisplay'); + + console.log('priceDisplay:', priceDisplay); + console.log('mobilePriceDisplay:', mobilePriceDisplay); + + if (priceDisplay) { + console.log('priceDisplay classes:', priceDisplay.className); + + // Тестируем показ блока + setTimeout(() => { + console.log('Showing price display...'); + priceDisplay.classList.remove('hidden'); + + // Обновляем цену + const currentPrice = document.getElementById('currentPrice'); + if (currentPrice) { + currentPrice.textContent = '₩500,000'; + } + }, 2000); + } + + // Добавим обработчики для карточек сервисов + document.querySelectorAll('.service-card').forEach(card => { + card.addEventListener('click', () => { + console.log('Service card clicked:', card.dataset.service); + + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + const currentPrice = document.getElementById('currentPrice'); + if (currentPrice) { + currentPrice.textContent = '₩750,000'; + } + } + + if (mobilePriceDisplay) { + mobilePriceDisplay.classList.remove('hidden'); + const mobilePriceValue = document.getElementById('mobilePriceValue'); + if (mobilePriceValue) { + mobilePriceValue.textContent = '₩750,000'; + } + } + }); + }); +}); \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index 0f44325..0e109a8 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -10,6 +10,102 @@ document.addEventListener('DOMContentLoaded', function() { }); } + // Theme Management + const themeToggle = document.getElementById('theme-toggle'); + const html = document.documentElement; + const slider = document.querySelector('.theme-toggle-slider'); + const sunIcon = document.querySelector('.theme-sun-icon'); + const moonIcon = document.querySelector('.theme-moon-icon'); + + // Get current theme from server or localStorage + const serverTheme = html.classList.contains('dark') ? 'dark' : 'light'; + const localTheme = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + // Priority: localStorage > server > system preference + const currentTheme = localTheme || serverTheme || (prefersDark ? 'dark' : 'light'); + + console.log('Theme initialization:', { serverTheme, localTheme, prefersDark, currentTheme }); + + // Apply theme and update toggle with smooth animation + function applyTheme(isDark, animate = false) { + console.log('Applying theme:', isDark ? 'dark' : 'light', 'animate:', animate); + + if (isDark) { + html.classList.add('dark'); + if (themeToggle) themeToggle.checked = true; + if (slider) { + // Расчет: ширина контейнера (56px) - ширина ползунка (20px) - отступы (8px) = 28px движения + slider.style.transform = 'translateX(28px)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to dark + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + sunIcon.style.transform = 'rotate(180deg) scale(0)'; + sunIcon.style.opacity = '0'; + moonIcon.style.transform = 'rotate(0deg) scale(1)'; + moonIcon.style.opacity = '1'; + } + } else { + html.classList.remove('dark'); + if (themeToggle) themeToggle.checked = false; + if (slider) { + slider.style.transform = 'translateX(0)'; + } + if (sunIcon && moonIcon && animate) { + // Animated transition to light + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } else if (sunIcon && moonIcon) { + // Instant set for initial load + moonIcon.style.transform = 'rotate(-180deg) scale(0)'; + moonIcon.style.opacity = '0'; + sunIcon.style.transform = 'rotate(0deg) scale(1)'; + sunIcon.style.opacity = '1'; + } + } + } + + // Initial theme application (без анимации при загрузке) + applyTheme(currentTheme === 'dark', false); + + // Theme toggle handler + function toggleTheme() { + const isDark = html.classList.contains('dark'); + const newTheme = isDark ? 'light' : 'dark'; + + console.log('Toggling theme from', isDark ? 'dark' : 'light', 'to', newTheme); + + applyTheme(!isDark, true); // С анимацией при клике + localStorage.setItem('theme', newTheme); + + // Send to server + fetch(`/theme/${newTheme}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) + .then(data => console.log('Theme synced to server:', data)) + .catch(error => { + console.warn('Theme sync failed:', error); + }); + } + + if (themeToggle) { + themeToggle.addEventListener('change', toggleTheme); + console.log('Theme toggle listener attached'); + } else { + console.warn('Theme toggle element not found'); + } + // Mobile Navigation Toggle const mobileMenuButton = document.querySelector('.mobile-menu-button'); const mobileMenu = document.querySelector('.mobile-menu'); diff --git a/public/js/price-island.js b/public/js/price-island.js new file mode 100644 index 0000000..e87857f --- /dev/null +++ b/public/js/price-island.js @@ -0,0 +1,289 @@ +// Dynamic Price Island Controller +class PriceIsland { + constructor() { + this.island = document.getElementById('priceIsland'); + this.container = document.getElementById('islandContainer'); + this.content = document.getElementById('islandContent'); + this.totalPrice = document.getElementById('totalPrice'); + + this.selectedServices = []; + this.projectDetails = {}; + this.appliedPromo = null; + this.isExpanded = false; + + this.init(); + } + + init() { + // Listen for calculator changes + document.addEventListener('calculatorStateChange', (e) => { + this.updateIsland(e.detail); + }); + + // Initialize promo code functionality + this.initPromoCode(); + + // Show island initially as compact + this.showCompact(); + } + + updateIsland(calculatorData) { + const { selectedServices, complexity, timeline, totalPrice } = calculatorData; + + this.selectedServices = selectedServices || []; + this.projectDetails = { complexity, timeline }; + + // Update total price + this.updateTotalPrice(totalPrice || 0); + + // Update content + this.updateContent(); + + // Expand if there's data to show + if (this.selectedServices.length > 0 || complexity || timeline) { + this.expand(); + } else { + this.collapse(); + } + } + + updateTotalPrice(price) { + if (this.totalPrice) { + this.totalPrice.textContent = this.formatPrice(price); + this.totalPrice.classList.add('price-update'); + setTimeout(() => { + this.totalPrice.classList.remove('price-update'); + }, 300); + } + } + + updateContent() { + this.updateSelectedServices(); + this.updateProjectDetails(); + this.updatePriceBreakdown(); + } + + updateSelectedServices() { + const container = document.getElementById('selectedServices'); + const list = document.getElementById('servicesList'); + + if (this.selectedServices.length > 0) { + container.classList.remove('hidden'); + list.innerHTML = this.selectedServices.map(service => ` +
+ ${service.name} + ${this.formatPrice(service.price)} +
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + updateProjectDetails() { + const container = document.getElementById('projectDetails'); + const list = document.getElementById('detailsList'); + + const details = []; + if (this.projectDetails.complexity) { + details.push(`Сложность: ${this.projectDetails.complexity.name} (×${this.projectDetails.complexity.multiplier})`); + } + if (this.projectDetails.timeline) { + details.push(`Сроки: ${this.projectDetails.timeline.name} (×${this.projectDetails.timeline.multiplier})`); + } + + if (details.length > 0) { + container.classList.remove('hidden'); + list.innerHTML = details.map(detail => ` +
${detail}
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + updatePriceBreakdown() { + const container = document.getElementById('priceBreakdown'); + const list = document.getElementById('breakdownList'); + + if (this.selectedServices.length > 0) { + container.classList.remove('hidden'); + + const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0); + const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1; + const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1; + + const afterComplexity = basePrice * complexityMultiplier; + const final = afterComplexity * timelineMultiplier; + + let breakdown = [ + { label: 'Базовая стоимость', value: basePrice } + ]; + + if (complexityMultiplier !== 1) { + breakdown.push({ + label: `Сложность (×${complexityMultiplier})`, + value: afterComplexity + }); + } + + if (timelineMultiplier !== 1) { + breakdown.push({ + label: `Сроки (×${timelineMultiplier})`, + value: final + }); + } + + if (this.appliedPromo) { + const discount = final * (this.appliedPromo.discount / 100); + breakdown.push({ + label: `Скидка ${this.appliedPromo.code} (-${this.appliedPromo.discount}%)`, + value: final - discount, + isDiscount: true + }); + } + + list.innerHTML = breakdown.map(item => ` +
+ ${item.label} + ${this.formatPrice(item.value)} +
+ `).join(''); + } else { + container.classList.add('hidden'); + } + } + + initPromoCode() { + const promoBtn = document.getElementById('applyPromoBtn'); + const promoInput = document.getElementById('promoInput'); + const promoStatus = document.getElementById('promoStatus'); + + if (promoBtn && promoInput) { + promoBtn.addEventListener('click', () => { + const code = promoInput.value.trim().toUpperCase(); + this.applyPromoCode(code); + }); + + promoInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + const code = promoInput.value.trim().toUpperCase(); + this.applyPromoCode(code); + } + }); + } + } + + applyPromoCode(code) { + const promoStatus = document.getElementById('promoStatus'); + + // Simulate promo code validation + const validCodes = { + 'WELCOME10': { discount: 10, name: 'Скидка новичка' }, + 'SAVE20': { discount: 20, name: 'Экономия 20%' }, + 'VIP15': { discount: 15, name: 'VIP скидка' } + }; + + if (validCodes[code]) { + this.appliedPromo = { code, ...validCodes[code] }; + promoStatus.textContent = `✓ Применен: ${this.appliedPromo.name}`; + promoStatus.className = 'text-xs mt-1 text-green-600 dark:text-green-400'; + + // Trigger recalculation + this.updateContent(); + this.recalculateTotal(); + } else if (code) { + promoStatus.textContent = '✗ Неверный промокод'; + promoStatus.className = 'text-xs mt-1 text-red-600 dark:text-red-400'; + } else { + promoStatus.textContent = ''; + } + } + + recalculateTotal() { + if (this.selectedServices.length > 0) { + const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0); + const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1; + const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1; + + let total = basePrice * complexityMultiplier * timelineMultiplier; + + if (this.appliedPromo) { + total = total * (1 - this.appliedPromo.discount / 100); + } + + this.updateTotalPrice(total); + + // Also update mobile price if exists + const mobilePrice = document.getElementById('mobilePriceValue'); + if (mobilePrice) { + mobilePrice.textContent = this.formatPrice(total); + } + } + } + + expand() { + if (!this.isExpanded) { + this.isExpanded = true; + this.content.style.maxHeight = this.content.scrollHeight + 'px'; + + // Show promo section when expanded + const promoSection = document.getElementById('promoSection'); + if (promoSection) { + promoSection.classList.remove('hidden'); + } + } + } + + collapse() { + if (this.isExpanded) { + this.isExpanded = false; + this.content.style.maxHeight = '0'; + + // Hide promo section when collapsed + const promoSection = document.getElementById('promoSection'); + if (promoSection) { + promoSection.classList.add('hidden'); + } + } + } + + showCompact() { + this.island.classList.remove('hidden'); + } + + hide() { + this.island.classList.add('hidden'); + } + + formatPrice(price) { + return new Intl.NumberFormat('ko-KR', { + style: 'currency', + currency: 'KRW', + maximumFractionDigits: 0 + }).format(price); + } +} + +// CSS for price update animation +const style = document.createElement('style'); +style.textContent = ` + .price-update { + animation: priceUpdate 0.3s ease-out; + } + + @keyframes priceUpdate { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } + } +`; +document.head.appendChild(style); + +// Initialize when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + window.priceIsland = new PriceIsland(); +}); + +// Export for use in calculator +window.PriceIsland = PriceIsland; \ No newline at end of file diff --git a/public/js/sticky-price.js b/public/js/sticky-price.js new file mode 100644 index 0000000..ee6c3ea --- /dev/null +++ b/public/js/sticky-price.js @@ -0,0 +1,107 @@ +// Enhanced Sticky Price Display +document.addEventListener('DOMContentLoaded', function() { + const priceDisplay = document.getElementById('priceDisplay'); + const currentPrice = document.getElementById('currentPrice'); + + if (!priceDisplay) return; + + // Show price display when calculator starts + function showPriceDisplay() { + if (priceDisplay.classList.contains('hidden')) { + priceDisplay.classList.remove('hidden'); + priceDisplay.classList.add('visible'); + } + } + + // Enhanced scroll behavior + let scrollTimeout; + window.addEventListener('scroll', function() { + const scrollY = window.scrollY; + + // Show/hide based on scroll position + if (scrollY > 100) { + showPriceDisplay(); + priceDisplay.classList.add('scrolled'); + } else { + priceDisplay.classList.remove('scrolled'); + } + + // Clear previous timeout + clearTimeout(scrollTimeout); + + // Add subtle bounce effect + scrollTimeout = setTimeout(() => { + priceDisplay.style.transform = 'translateX(0) scale(1)'; + }, 150); + }); + + // Price update animation + const originalUpdatePrice = window.updatePrice || function() {}; + window.updatePrice = function(price, animate = true) { + if (currentPrice && animate) { + // Add updating animation + currentPrice.classList.add('updating'); + + // Update price after brief delay + setTimeout(() => { + currentPrice.textContent = price; + currentPrice.classList.remove('updating'); + }, 150); + } else if (currentPrice) { + currentPrice.textContent = price; + } + + // Show price display when price is calculated + showPriceDisplay(); + + // Call original function if it exists + if (typeof originalUpdatePrice === 'function') { + originalUpdatePrice(price, animate); + } + }; + + // Monitor calculator interactions + const calculator = document.querySelector('[data-calculator]') || document.querySelector('.calculator-container'); + if (calculator) { + // Show price display when user interacts with calculator + const inputs = calculator.querySelectorAll('input, select, button'); + inputs.forEach(input => { + input.addEventListener('change', showPriceDisplay); + input.addEventListener('click', showPriceDisplay); + }); + } + + // Enhanced visibility on mobile + function handleMobileVisibility() { + if (window.innerWidth <= 1024) { + priceDisplay.classList.remove('fixed'); + priceDisplay.classList.add('relative'); + } else { + priceDisplay.classList.remove('relative'); + priceDisplay.classList.add('fixed'); + } + } + + // Initial check and resize listener + handleMobileVisibility(); + window.addEventListener('resize', handleMobileVisibility); + + // Smooth reveal animation + setTimeout(() => { + if (priceDisplay && !priceDisplay.classList.contains('hidden')) { + priceDisplay.style.transition = 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)'; + } + }, 100); + + // Auto-show after initial page load + setTimeout(showPriceDisplay, 1000); +}); + +// Helper function for other scripts to trigger price display +window.showStickyPrice = function() { + const priceDisplay = document.getElementById('priceDisplay'); + if (priceDisplay) { + priceDisplay.classList.remove('hidden'); + priceDisplay.classList.add('visible'); + } +}; \ No newline at end of file diff --git a/public/sw.js b/public/sw.js index 94421c1..ea47299 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,7 +1,7 @@ // Service Worker for SmartSolTech PWA -const CACHE_NAME = 'smartsoltech-v1.0.1'; -const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.1'; -const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.1'; +const CACHE_NAME = 'smartsoltech-v1.0.2'; +const STATIC_CACHE_NAME = 'smartsoltech-static-v1.0.2'; +const DYNAMIC_CACHE_NAME = 'smartsoltech-dynamic-v1.0.2'; // Files to cache immediately const STATIC_FILES = [ @@ -101,8 +101,9 @@ self.addEventListener('fetch', event => { event.respondWith(cacheFirst(request)); } else if (isAPIRequest(request.url)) { event.respondWith(networkFirst(request)); - } else if (isDynamicRoute(request.url)) { - event.respondWith(staleWhileRevalidate(request)); + } else if (isDynamicRoute(request.url) || url.pathname === '/') { + // For main pages, always check network first to ensure language consistency + event.respondWith(networkFirst(request)); } else { event.respondWith(networkFirst(request)); } diff --git a/routes/admin.js b/routes/admin.js index 529109a..1e5f124 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -130,7 +130,8 @@ router.get('/dashboard', requireAuth, async (req, res) => { user: req.session.user, stats, recentContacts, - recentPortfolio + recentPortfolio, + currentPage: 'dashboard' }); } catch (error) { console.error('Dashboard error:', error); diff --git a/routes/index.js b/routes/index.js index cd5dfb4..46e9267 100644 --- a/routes/index.js +++ b/routes/index.js @@ -193,7 +193,7 @@ router.get('/services', async (req, res) => { } }); -// Calculator page +// Modern Calculator page (new UX-polished version) router.get('/calculator', async (req, res) => { try { const [settings, services] = await Promise.all([ @@ -205,14 +205,15 @@ router.get('/calculator', async (req, res) => { }) ]); - res.render('calculator', { - title: 'Project Calculator - SmartSolTech', + res.render('calculator-modern', { + title: 'Калькулятор стоимости услуг - SmartSolTech', settings: settings || {}, services, - currentPage: 'calculator' + currentPage: 'calculator', + siteSettings: settings || {} }); } catch (error) { - console.error('Calculator page error:', error); + console.error('Modern calculator page error:', error); res.status(500).render('error', { title: 'Error', settings: {}, diff --git a/server-demo.js b/server-demo.js deleted file mode 100644 index 0a61a18..0000000 --- a/server-demo.js +++ /dev/null @@ -1,486 +0,0 @@ -/** - * 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 i18n = require('i18n'); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Configure i18n -i18n.configure({ - locales: ['en', 'ko', 'ru', 'kk'], - directory: path.join(__dirname, 'locales'), - defaultLocale: 'ko', - cookie: 'language', - queryParameter: 'lang', - autoReload: true, - syncFiles: true, - objectNotation: true -}); - -// 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'))); - -// Initialize i18n -app.use(i18n.init); - -// 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; -} - -// Language switching route -app.get('/lang/:language', (req, res) => { - const language = req.params.language; - const supportedLanguages = ['en', 'ko', 'ru', 'kk']; - - if (supportedLanguages.includes(language)) { - res.cookie('language', language, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - req.setLocale(language); - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Middleware to set language and theme preferences -app.use((req, res, next) => { - // Set language - const language = req.cookies.language || req.query.lang || 'ko'; - if (['en', 'ko', 'ru', 'kk'].includes(language)) { - req.setLocale(language); - } - - // Set theme preference - const theme = req.cookies.theme || 'light'; - res.locals.theme = theme; - res.locals.currentLanguage = req.getLocale(); - res.locals.__ = res.__; - - next(); -}); - -// Theme switching route -app.get('/theme/:theme', (req, res) => { - const theme = req.params.theme; - if (['light', 'dark'].includes(theme)) { - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000 }); // 1 year - } - - const redirectUrl = req.get('Referer') || '/'; - res.redirect(redirectUrl); -}); - -// Routes -app.get('/', (req, res) => { - res.render('index', { - title: res.__('navigation.home') + ' - SmartSolTech', - 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: res.__('navigation.portfolio') + ' - 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 - ' + res.__('common.error'), - message: res.__('common.error'), - currentPage: 'error' - }); - } - - const relatedProjects = mockPortfolio.filter(p => - p._id !== portfolio._id && p.category === portfolio.category - ).slice(0, 3); - - res.render('portfolio-detail', { - title: `${portfolio.title} - ${res.__('navigation.portfolio')}`, - portfolio, - relatedProjects, - currentPage: 'portfolio' - }); -}); - -app.get('/services', (req, res) => { - res.render('services', { - title: res.__('navigation.services') + ' - SmartSolTech', - services: mockServices, - currentPage: 'services' - }); -}); - -app.get('/about', (req, res) => { - res.render('about', { - title: res.__('navigation.about') + ' - SmartSolTech', - currentPage: 'about' - }); -}); - -app.get('/contact', (req, res) => { - res.render('contact', { - title: res.__('navigation.contact') + ' - 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.__('common.success')); - res.redirect('/contact'); -}); - -app.get('/calculator', (req, res) => { - res.render('calculator', { - title: res.__('navigation.calculator') + ' - 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(' • About: http://localhost:3000/about'); - 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; \ No newline at end of file diff --git a/server.js b/server.js index 4c12c09..f6a1156 100644 --- a/server.js +++ b/server.js @@ -26,19 +26,6 @@ i18n.configure({ // i18n middleware app.use(i18n.init); -// Middleware для передачи переменных в шаблоны -app.use((req, res, next) => { - const currentLang = req.session?.language || req.getLocale() || 'ru'; - req.setLocale(currentLang); - - res.locals.locale = currentLang; - res.locals.__ = res.__; - res.locals.theme = req.session?.theme || 'light'; - res.locals.currentLanguage = currentLang; - res.locals.currentPage = req.path.split('/')[1] || 'home'; - next(); -}); - // Security middleware app.use(helmet({ contentSecurityPolicy: { @@ -106,6 +93,36 @@ app.use(session({ } })); +// Middleware для передачи переменных в шаблоны +app.use((req, res, next) => { + const currentLang = req.session?.language || req.getLocale() || 'ru'; + req.setLocale(currentLang); + + res.locals.locale = currentLang; + res.locals.__ = res.__; + res.locals.theme = req.session?.theme || 'light'; + res.locals.currentLanguage = currentLang; + res.locals.currentPage = req.path.split('/')[1] || 'home'; + + // Debug logging for theme + if (req.url !== '/sw.js' && !req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) { + console.log(`Request URL: ${req.url}, Theme from session: ${req.session?.theme}, Setting theme to: ${res.locals.theme}`); + } + + // Устанавливаем заголовки для предотвращения кеширования языкового контента + if (!req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + res.setHeader('Vary', 'Accept-Language'); + } + + // Отладочная информация + console.log(`Request URL: ${req.url}, Current Language: ${currentLang}, Session Language: ${req.session?.language}, Locale: ${req.getLocale()}`); + + next(); +}); + // Routes app.use('/', require('./routes/index')); app.use('/api/auth', require('./routes/auth')); @@ -125,6 +142,7 @@ app.get('/lang/:language', (req, res) => { if (supportedLanguages.includes(language)) { req.setLocale(language); req.session.language = language; + console.log(`Language switched to: ${language}, session language: ${req.session.language}`); } const referer = req.get('Referer') || '/'; diff --git a/src/tailwind.css b/src/tailwind.css new file mode 100644 index 0000000..539fc23 --- /dev/null +++ b/src/tailwind.css @@ -0,0 +1,132 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Custom base styles */ +@layer base { + html { + scroll-behavior: smooth; + } + + body { + @apply font-sans text-gray-900 bg-white; + } + + h1, h2, h3, h4, h5, h6 { + @apply font-display font-semibold; + } + + .container { + @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8; + } +} + +/* Custom component styles */ +@layer components { + .btn { + @apply inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors duration-200; + } + + .btn-primary { + @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500; + } + + .btn-secondary { + @apply bg-secondary-100 text-secondary-900 hover:bg-secondary-200 focus:ring-secondary-500; + } + + .btn-outline { + @apply bg-transparent border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-primary-500; + } + + .btn-sm { + @apply px-3 py-1.5 text-xs; + } + + .btn-lg { + @apply px-6 py-3 text-base; + } + + .card { + @apply bg-white rounded-lg border border-gray-200 shadow-sm; + } + + .card-body { + @apply p-6; + } + + .form-input { + @apply block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm; + } + + .form-label { + @apply block text-sm font-medium text-gray-700 mb-1; + } + + .form-error { + @apply mt-1 text-sm text-red-600; + } + + .badge { + @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; + } + + .badge-primary { + @apply bg-primary-100 text-primary-800; + } + + .badge-success { + @apply bg-green-100 text-green-800; + } + + .badge-warning { + @apply bg-yellow-100 text-yellow-800; + } + + .badge-error { + @apply bg-red-100 text-red-800; + } + + .alert { + @apply rounded-md p-4 border; + } + + .alert-success { + @apply bg-green-50 border-green-200 text-green-800; + } + + .alert-warning { + @apply bg-yellow-50 border-yellow-200 text-yellow-800; + } + + .alert-error { + @apply bg-red-50 border-red-200 text-red-800; + } + + .alert-info { + @apply bg-blue-50 border-blue-200 text-blue-800; + } +} + +/* Custom utility styles */ +@layer utilities { + .text-balance { + text-wrap: balance; + } + + .scroll-smooth { + scroll-behavior: smooth; + } + + .animate-fade-in { + animation: fadeIn 0.5s ease-in-out; + } + + .animate-slide-in { + animation: slideIn 0.3s ease-out; + } + + .animate-bounce-in { + animation: bounceIn 0.6s ease-out; + } +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..2e732a6 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,83 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./views/**/*.ejs", + "./public/**/*.js", + "./public/**/*.html" + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a' + }, + secondary: { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a' + } + }, + fontFamily: { + sans: ['Ubuntu', 'system-ui', 'sans-serif'], + display: ['Ubuntu', 'system-ui', 'sans-serif'] + }, + fontSize: { + 'xs': ['0.625rem', { lineHeight: '0.875rem' }], + 'sm': ['0.75rem', { lineHeight: '1rem' }], + 'base': ['0.825rem', { lineHeight: '1.125rem' }], + 'lg': ['0.9rem', { lineHeight: '1.25rem' }], + 'xl': ['1rem', { lineHeight: '1.375rem' }], + '2xl': ['1.125rem', { lineHeight: '1.5rem' }], + '3xl': ['1.375rem', { lineHeight: '1.75rem' }], + '4xl': ['1.625rem', { lineHeight: '2rem' }], + '5xl': ['2rem', { lineHeight: '2.5rem' }], + '6xl': ['2.5rem', { lineHeight: '3rem' }], + '7xl': ['3rem', { lineHeight: '3.5rem' }], + '8xl': ['4rem', { lineHeight: '4.5rem' }], + '9xl': ['5rem', { lineHeight: '5.5rem' }] + }, + spacing: { + '72': '18rem', + '84': '21rem', + '96': '24rem' + }, + animation: { + 'fade-in': 'fadeIn 0.5s ease-in-out', + 'slide-in': 'slideIn 0.3s ease-out', + 'bounce-in': 'bounceIn 0.6s ease-out' + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + slideIn: { + '0%': { transform: 'translateY(-10px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' } + }, + bounceIn: { + '0%': { transform: 'scale(0.3)', opacity: '0' }, + '50%': { transform: 'scale(1.05)', opacity: '0.8' }, + '100%': { transform: 'scale(1)', opacity: '1' } + } + } + }, + }, + plugins: [] +} \ No newline at end of file diff --git a/views/about-old.ejs b/views/about-old.ejs deleted file mode 100644 index 0974833..0000000 --- a/views/about-old.ejs +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - 회사 소개 - SmartSolTech - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
-

- About SmartSolTech -

-

- 혁신적인 기술로 고객의 성공을 이끌어가는 디지털 솔루션 전문 기업 -

-
-
- - -
-
-
-
-

- 혁신창의로 만드는 미래 -

-

- SmartSolTech는 2020년 설립된 디지털 솔루션 전문 기업으로, 웹 개발, 모바일 앱, UI/UX 디자인 분야에서 - 혁신적인 기술과 창의적인 아이디어를 바탕으로 고객의 비즈니스 성공을 지원합니다. -

-

- 우리는 단순히 기술을 제공하는 것이 아니라, 고객의 목표를 이해하고 그에 맞는 최적의 솔루션을 - 제안하여 함께 성장하는 파트너가 되고자 합니다. -

-
-
-
100+
-
완료 프로젝트
-
-
-
50+
-
만족한 고객
-
-
-
4년
-
업계 경험
-
-
-
-
-
-
-

우리의 미션

-

- "기술을 통해 모든 비즈니스가 디지털 시대에서 성공할 수 있도록 돕는 것" -

-

우리의 비전

-

- "한국을 대표하는 글로벌 디지털 솔루션 기업으로 성장하여 - 전 세계 고객들의 디지털 혁신을 주도하는 것" -

-
-
-
-
-
-
-
-
- - -
-
-
-

- Core Values -

-

- SmartSolTech가 추구하는 핵심 가치들 -

-
- -
-
-
- -
-

혁신 (Innovation)

-

- 끊임없는 연구개발과 최신 기술 도입으로 혁신적인 솔루션을 제공합니다. -

-
- -
-
- -
-

협력 (Collaboration)

-

- 고객과의 긴밀한 소통과 협력을 통해 최상의 결과를 만들어냅니다. -

-
- -
-
- -
-

품질 (Quality)

-

- 높은 품질 기준을 유지하며 고객이 만족할 수 있는 완성도 높은 제품을 제공합니다. -

-
- -
-
- -
-

성장 (Growth)

-

- 고객과 함께 성장하며, 지속적인 학습과 발전을 추구합니다. -

-
-
-
-
- - -
-
-
-

- Our Team -

-

- 전문성과 열정을 갖춘 SmartSolTech 팀을 소개합니다 -

-
- -
-
-
- KH -
-

김현우

-

CEO & Founder

-

- 10년 이상의 웹 개발 경험을 바탕으로 SmartSolTech를 설립하여 - 혁신적인 디지털 솔루션을 제공하고 있습니다. -

- -
- -
-
- LJ -
-

이정민

-

CTO & Lead Developer

-

- 풀스택 개발자로서 최신 기술 트렌드를 적용하여 - 확장 가능하고 안정적인 시스템을 구축합니다. -

- -
- -
-
- PS -
-

박서연

-

UI/UX Designer

-

- 사용자 중심의 디자인 철학을 바탕으로 직관적이고 - 아름다운 인터페이스를 설계합니다. -

- -
-
-
-
- - -
-
-
-

- Technology Stack -

-

- 최신 기술과 검증된 도구들로 최고의 솔루션을 제공합니다 -

-
- -
-
-

Frontend

-
-
- -
React
-
-
- -
Vue.js
-
-
- -
Angular
-
-
-
- -
-

Backend

-
-
- -
Node.js
-
-
- -
Python
-
-
- -
Java
-
-
-
- -
-

Mobile

-
-
- -
React Native
-
-
- -
Flutter
-
-
- -
Swift
-
-
-
-
-
-
- - -
-
-
-

- 함께 성공하는 파트너가 되어보세요 -

-

- SmartSolTech와 함께 여러분의 비즈니스를 다음 단계로 발전시켜보세요 -

- -
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/views/about.ejs b/views/about.ejs index 8a673aa..e52df18 100644 --- a/views/about.ejs +++ b/views/about.ejs @@ -1,22 +1,4 @@ - - - - - - <%- __('about.meta.title') %> - SmartSolTech - - - - - - - - - - - - - <%- include('partials/navigation') %> +
@@ -39,10 +21,10 @@

<%- __('about.company.title') %>

-

+

<%- __('about.company.description1') %>

-

+

<%- __('about.company.description2') %>

@@ -80,7 +62,7 @@

<%- __('about.mission.title') %>

-

+

<%- __('about.mission.description') %>

@@ -146,15 +128,12 @@
- <%- include('partials/footer') %> + - - - - \ No newline at end of file + \ No newline at end of file diff --git a/views/admin/dashboard.ejs b/views/admin/dashboard.ejs index a72cc3c..ede4996 100644 --- a/views/admin/dashboard.ejs +++ b/views/admin/dashboard.ejs @@ -1,93 +1,4 @@ - - - - - - <%= title %> - SmartSolTech Admin - - - - - - - - - - - - - -
-
-
-
-

- - SmartSolTech Admin -

-
-
- - Добро пожаловать, <%= user ? user.name : 'Admin' %>! - - - - Посмотреть сайт - -
- -
-
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/views/admin/layout.ejs b/views/admin/layout.ejs index dab51d1..ac0c487 100644 --- a/views/admin/layout.ejs +++ b/views/admin/layout.ejs @@ -6,7 +6,7 @@ <%= title %> - SmartSolTech Admin - + @@ -56,7 +56,7 @@
- <%- include('partials/footer') %> + - - - - \ No newline at end of file + }); + \ No newline at end of file diff --git a/views/index-old.ejs b/views/index-old.ejs deleted file mode 100644 index a95ab66..0000000 --- a/views/index-old.ejs +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - <%= title || 'SmartSolTech - 혁신적인 기술 솔루션' %> - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> - - -
-
-
- - -
-
-
-
-
- -
-
-

- Smart Technology - Solutions -

-

- 혁신적인 웹 개발, 모바일 앱, UI/UX 디자인으로 비즈니스의 디지털 전환을 이끌어갑니다 -

- -
- - -
- - - -
-
-
- - -
-
-
-

- Our Services -

-

- 최신 기술과 창의적인 아이디어로 완성하는 디지털 솔루션 -

-
- -
- -
-
- -
-

웹 개발

-

현대적이고 반응형 웹사이트 및 웹 애플리케이션 개발

-
₩500,000~
-
- -
-
- -
-

모바일 앱

-

iOS와 Android를 위한 네이티브 및 크로스플랫폼 앱

-
₩800,000~
-
- -
-
- -
-

UI/UX 디자인

-

사용자 중심의 직관적이고 아름다운 인터페이스 디자인

-
₩300,000~
-
- -
-
- -
-

디지털 마케팅

-

SEO, 소셜미디어, 온라인 광고를 통한 디지털 마케팅

-
₩200,000~
-
-
- - -
-
- - -
-
-
-

- Recent Projects -

-

- 고객의 성공을 위해 완성한 프로젝트들을 확인해보세요 -

-
- -
- <% if (featuredPortfolio && featuredPortfolio.length > 0) { %> - <% featuredPortfolio.forEach((project, index) => { %> -
-
- <% if (project.images && project.images.length > 0) { %> - <%= project.images.find(img => img.isPrimary)?.alt || project.title %> - <% } else { %> -
- <%= project.title.charAt(0) %> -
- <% } %> -
-
-
- <% if (project.technologies && project.technologies.length > 0) { %> - <% project.technologies.slice(0, 3).forEach(tech => { %> - <%= tech %> - <% }) %> - <% } %> -
-
-
-
-
- <%= project.category %> - - <%= project.viewCount || 0 %> - -
-

<%= project.title %>

-

<%= project.shortDescription || project.description %>

- - 자세히 보기 - - - - -
-
- <% }) %> - <% } else { %> - -
-
-
- E -
-
-
-
- React - Node.js -
-
-
-
-
- E-commerce - - 1,234 - -
-

온라인 쇼핑몰 플랫폼

-

현대적인 UI/UX와 완벽한 결제 시스템을 갖춘 전자상거래 솔루션

- - 자세히 보기 - - - - -
-
- <% } %> -
- - -
-
- - -
-
-
-

- 프로젝트 견적을 확인해보세요 -

-

- 원하는 서비스와 요구사항을 선택하면 실시간으로 견적을 계산해드립니다 -

- - - 견적 계산기 사용하기 - -
-
-
- - -
-
-
-
-

- 프로젝트를 시작할 준비가 되셨나요? -

-

- 아이디어를 현실로 만들어보세요. 전문가들이 최고의 솔루션을 제공합니다. -

-
-
-
- -
-
-
전화 상담
-
+82-10-0000-0000
-
-
-
-
- -
-
-
이메일 문의
-
info@smartsoltech.kr
-
-
-
-
- -
-
-
텔레그램 채팅
-
즉시 답변 가능
-
-
-
-
-
-
-

무료 상담 신청

-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
-
-
-
-
- - <%- include('partials/footer') %> - - - - - - - \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index f6fcb0c..580f530 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -1,58 +1,4 @@ - - - - - - <%- title || 'SmartSolTech - Innovative Technology Solutions' %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <%- include('partials/navigation') %> +
@@ -68,7 +14,7 @@
-

+

<%- __('hero.title.smart') %> <%- __('hero.title.solutions') %>

@@ -98,10 +44,10 @@
-

+

<%- __('services.title.our') %> <%- __('services.title.services') %>

-

+

<%- __('services.subtitle') %>

@@ -163,10 +109,10 @@
-

+

<%- __('portfolio.title.recent') %> <%- __('portfolio.title.projects') %>

-

+

<%- __('portfolio.subtitle') %>

@@ -264,10 +210,10 @@
- <%- include('partials/footer') %> - - - - - - - - - - - - - \ No newline at end of file + }); + \ No newline at end of file diff --git a/views/layout.ejs b/views/layout.ejs index 93d1e0b..358014e 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -33,10 +33,10 @@ - + - - + + @@ -48,24 +48,10 @@ + + - - + @@ -109,5 +95,8 @@ gtag('config', '<%= settings.seo.googleAnalytics %>'); <% } %> + + + <%- script %> \ No newline at end of file diff --git a/views/partials/footer-old.ejs b/views/partials/footer-old.ejs deleted file mode 100644 index fb5f61d..0000000 --- a/views/partials/footer-old.ejs +++ /dev/null @@ -1,103 +0,0 @@ - \ No newline at end of file diff --git a/views/partials/navigation-old.ejs b/views/partials/navigation-old.ejs deleted file mode 100644 index 4bf6bde..0000000 --- a/views/partials/navigation-old.ejs +++ /dev/null @@ -1,80 +0,0 @@ - - - -
\ No newline at end of file diff --git a/views/partials/navigation.ejs b/views/partials/navigation.ejs index 669611d..e1471e6 100644 --- a/views/partials/navigation.ejs +++ b/views/partials/navigation.ejs @@ -36,12 +36,20 @@
- -
+ +
- <%- include('partials/footer') %> + @@ -469,6 +452,4 @@ }; return categoryNames[category] || category; } - %> - - \ No newline at end of file + %> \ No newline at end of file diff --git a/views/portfolio.ejs b/views/portfolio.ejs index 4a16fd9..5e4cc76 100644 --- a/views/portfolio.ejs +++ b/views/portfolio.ejs @@ -1,34 +1,9 @@ - - - - - - <%- __('portfolio.meta.title') %> - SmartSolTech - - - - - - - - - - - - - - - + - - - - - - <%- include('partials/navigation') %> +
@@ -197,11 +172,10 @@
- <%- include('partials/footer') %> + - - - + + - - - \ No newline at end of file + + \ No newline at end of file