first
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# Next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# Production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env*.local
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# TypeScript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
.git
|
||||||
|
.env*.local
|
||||||
|
*.log
|
||||||
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Romanovna Photo - Сайт-портфолио фотографа
|
||||||
|
|
||||||
|
Минималистичный, эстетичный, тёмно-винтажный сайт-витрина для фотографа Ангелины Чёрной.
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### Установка зависимостей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Запуск в режиме разработки
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Откройте [http://localhost:3000](http://localhost:3000) в браузере.
|
||||||
|
|
||||||
|
### Сборка для продакшена
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
romanovna-photo/
|
||||||
|
├── public/
|
||||||
|
│ ├── logo/
|
||||||
|
│ │ └── romanovna-logo.png # Логотип (уже добавлен)
|
||||||
|
│ └── images/
|
||||||
|
│ ├── carousel/ # Изображения для карусели (6 шт.)
|
||||||
|
│ └── services/ # Обложки и примеры для услуг
|
||||||
|
├── src/
|
||||||
|
│ ├── app/ # Страницы Next.js
|
||||||
|
│ ├── components/ # React компоненты
|
||||||
|
│ ├── lib/ # Конфигурационные данные
|
||||||
|
│ └── styles/ # CSS модули
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🖼️ Добавление изображений
|
||||||
|
|
||||||
|
### Карусель работ
|
||||||
|
|
||||||
|
Добавьте 6 изображений в `public/images/carousel/`:
|
||||||
|
- `work-1.jpg`
|
||||||
|
- `work-2.jpg`
|
||||||
|
- `work-3.jpg`
|
||||||
|
- `work-4.jpg`
|
||||||
|
- `work-5.jpg`
|
||||||
|
- `work-6.jpg`
|
||||||
|
|
||||||
|
### Услуги
|
||||||
|
|
||||||
|
Для каждой услуги добавьте изображения в `public/images/services/`:
|
||||||
|
|
||||||
|
**Фотосессия на улице (street):**
|
||||||
|
- `street-cover.jpg` (обложка)
|
||||||
|
- `street-example-1.jpg`
|
||||||
|
- `street-example-2.jpg`
|
||||||
|
- `street-example-3.jpg`
|
||||||
|
|
||||||
|
**Фотосессия в студии (studio):**
|
||||||
|
- `studio-cover.jpg` (обложка)
|
||||||
|
- `studio-example-1.jpg`
|
||||||
|
- `studio-example-2.jpg`
|
||||||
|
|
||||||
|
**Ретушь (retouch):**
|
||||||
|
- `retouch-cover.jpg` (обложка)
|
||||||
|
- `retouch-example-1.jpg`
|
||||||
|
- `retouch-example-2.jpg`
|
||||||
|
|
||||||
|
**Сертификат (certificate):**
|
||||||
|
- `certificate-cover.jpg` (обложка)
|
||||||
|
- `certificate-example-1.jpg`
|
||||||
|
|
||||||
|
## 🗺️ Настройка Яндекс.Карты
|
||||||
|
|
||||||
|
✅ Яндекс.Карта уже настроена и готова к использованию!
|
||||||
|
|
||||||
|
Если нужно изменить карту:
|
||||||
|
1. Создайте новую карту на [Яндекс.Конструкторе карт](https://yandex.ru/map-constructor/)
|
||||||
|
2. Скопируйте код скрипта
|
||||||
|
3. Замените URL скрипта в `src/components/contacts/YandexMap.tsx`
|
||||||
|
|
||||||
|
## 🎨 Цветовая палитра
|
||||||
|
|
||||||
|
- **Акцентный**: `#A64456` (винтажный красно-розовый)
|
||||||
|
- **Песочный**: `#BF9B7A`
|
||||||
|
- **Светло-винтажный**: `#D9B79A`
|
||||||
|
- **Коричневый винтаж**: `#8C654F`
|
||||||
|
- **Тёмный**: `#594336`
|
||||||
|
- **Фон**: `#1a1a1a`
|
||||||
|
|
||||||
|
## 📝 Настройка данных
|
||||||
|
|
||||||
|
Все данные о компании, услугах и галерее находятся в `src/lib/`:
|
||||||
|
- `company.ts` - информация о компании
|
||||||
|
- `services.ts` - список услуг
|
||||||
|
- `gallery.ts` - изображения для карусели
|
||||||
|
|
||||||
|
## 🛠️ Технологии
|
||||||
|
|
||||||
|
- **Next.js 14** - React фреймворк
|
||||||
|
- **TypeScript** - типизация
|
||||||
|
- **CSS Modules** - стилизация
|
||||||
|
- **Google Fonts** - типографика (Playfair Display, Inter)
|
||||||
|
|
||||||
|
## 📱 Адаптивность
|
||||||
|
|
||||||
|
Сайт полностью адаптирован для:
|
||||||
|
- Desktop (1200px+)
|
||||||
|
- Tablet (768px - 1199px)
|
||||||
|
- Mobile (до 767px)
|
||||||
|
|
||||||
|
## 📄 Лицензия
|
||||||
|
|
||||||
|
Проект создан для фотографа Ангелины Чёрной (@romanovnaph_ch).
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
images: {
|
||||||
|
formats: ['image/avif', 'image/webp'],
|
||||||
|
remotePatterns: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
images: {
|
||||||
|
formats: ['image/webp', 'image/avif'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,498 @@
|
||||||
|
{
|
||||||
|
"name": "romanovna-photo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "romanovna-photo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"next": "^14.2.0",
|
||||||
|
"react": "^18.3.0",
|
||||||
|
"react-dom": "^18.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/env": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-CgVHNZ1fRIlxkLhIX22flAZI/HmpDaZ8vwyJ/B0SDPTBuLZ1PJ+DWMjCHhqnExfmSQzA/PbZi8OAc7PAq2w9IA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/counter": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@swc/helpers": {
|
||||||
|
"version": "0.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
|
||||||
|
"integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.19.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
|
||||||
|
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/prop-types": {
|
||||||
|
"version": "15.7.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "18.3.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||||
|
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "18.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||||
|
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/busboy": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"streamsearch": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/caniuse-lite": {
|
||||||
|
"version": "1.0.30001759",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
|
||||||
|
"integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
|
},
|
||||||
|
"node_modules/client-only": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/graceful-fs": {
|
||||||
|
"version": "4.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/js-tokens": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next": {
|
||||||
|
"version": "14.2.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz",
|
||||||
|
"integrity": "sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@next/env": "14.2.33",
|
||||||
|
"@swc/helpers": "0.5.5",
|
||||||
|
"busboy": "1.6.0",
|
||||||
|
"caniuse-lite": "^1.0.30001579",
|
||||||
|
"graceful-fs": "^4.2.11",
|
||||||
|
"postcss": "8.4.31",
|
||||||
|
"styled-jsx": "5.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"next": "dist/bin/next"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@next/swc-darwin-arm64": "14.2.33",
|
||||||
|
"@next/swc-darwin-x64": "14.2.33",
|
||||||
|
"@next/swc-linux-arm64-gnu": "14.2.33",
|
||||||
|
"@next/swc-linux-arm64-musl": "14.2.33",
|
||||||
|
"@next/swc-linux-x64-gnu": "14.2.33",
|
||||||
|
"@next/swc-linux-x64-musl": "14.2.33",
|
||||||
|
"@next/swc-win32-arm64-msvc": "14.2.33",
|
||||||
|
"@next/swc-win32-ia32-msvc": "14.2.33",
|
||||||
|
"@next/swc-win32-x64-msvc": "14.2.33"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@opentelemetry/api": "^1.1.0",
|
||||||
|
"@playwright/test": "^1.41.2",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"sass": "^1.3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@opentelemetry/api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@playwright/test": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sass": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/picocolors": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/postcss": {
|
||||||
|
"version": "8.4.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
|
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.6",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"scheduler": "^0.23.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.23.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/streamsearch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/styled-jsx": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"client-only": "0.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@babel/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"babel-plugin-macros": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "romanovna-photo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "^14.2.0",
|
||||||
|
"react": "^18.3.0",
|
||||||
|
"react-dom": "^18.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
# AI Prompts для генерации обложек услуг
|
||||||
|
## Стилистика сайта Romanovna Photography
|
||||||
|
|
||||||
|
**Цветовая палитра:**
|
||||||
|
- Accent: #A64456 (винтажный красно-розовый)
|
||||||
|
- Sand: #BF9B7A (тёплый песочный)
|
||||||
|
- Light Vintage: #D9B79A (светло-винтажный беж)
|
||||||
|
- Brown Vintage: #8C654F (коричневый винтаж)
|
||||||
|
- Dark: #594336 (глубокий коричневый)
|
||||||
|
- Background: #1a1a1a (почти чёрный)
|
||||||
|
|
||||||
|
**Общее настроение:** Тёмная винтажная эстетика, кинематографичность, минимализм, элегантность
|
||||||
|
|
||||||
|
**Слоган:** "Эстетика в каждом кадре"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Фотосессия на улице (street-cover.jpg)
|
||||||
|
|
||||||
|
### Промпт для Midjourney/DALL-E:
|
||||||
|
|
||||||
|
```
|
||||||
|
Cinematic street photography scene, elegant young woman in vintage outfit walking on cobblestone European street at golden hour, warm sunset light, bokeh background with old architecture, film grain texture, muted color grading with warm tones (sand #BF9B7A, vintage brown #8C654F), atmospheric haze, shot on film camera aesthetic, vintage rose accents #A64456, professional fashion photography, dreamy and romantic mood, shallow depth of field, 16:9 aspect ratio, photorealistic --ar 16:9 --style raw --stylize 300
|
||||||
|
```
|
||||||
|
|
||||||
|
### Русский промпт:
|
||||||
|
|
||||||
|
```
|
||||||
|
Кинематографичная уличная фотография, элегантная молодая женщина в винтажном наряде идет по европейской мощеной улице на закате, теплый золотой час, боке-фон со старинной архитектурой, зернистость плёнки, приглушенная цветокоррекция в тёплых тонах (песочный, винтажный коричневый), атмосферная дымка, эстетика плёночной камеры, винтажные розовые акценты, профессиональная fashion фотография, мечтательное романтичное настроение, малая глубина резкости, соотношение 16:9, фотореалистично
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые элементы:
|
||||||
|
- Золотой час / закатный свет
|
||||||
|
- Европейская архитектура (старые улицы)
|
||||||
|
- Винтажная одежда
|
||||||
|
- Тёплая цветокоррекция
|
||||||
|
- Боке и малая глубина резкости
|
||||||
|
- Эффект плёнки
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Фотосессия в студии (studio-cover.jpg)
|
||||||
|
|
||||||
|
### Промпт для Midjourney/DALL-E:
|
||||||
|
|
||||||
|
```
|
||||||
|
Professional studio portrait photography, elegant fashion model in minimalist vintage-style clothing, dramatic studio lighting setup with softbox and rim light, dark moody background (#1a1a1a), warm accent lighting (#A64456 rose tint), high-end fashion editorial style, sharp focus on face, sophisticated pose, muted warm color palette (sand #BF9B7A, vintage brown tones), cinematic atmosphere, beauty dish lighting, professional retouching aesthetic, 16:9 aspect ratio --ar 16:9 --style raw --q 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Русский промпт:
|
||||||
|
|
||||||
|
```
|
||||||
|
Профессиональная студийная портретная фотография, элегантная fashion-модель в минималистичной винтажной одежде, драматичное студийное освещение с софтбоксами и контровым светом, тёмный мрачный фон, тёплый акцентный свет с розовым оттенком, high-end модный editorial стиль, резкий фокус на лице, изысканная поза, приглушенная тёплая цветовая палитра (песочный, винтажные коричневые тона), кинематографичная атмосфера, освещение beauty dish, эстетика профессиональной ретуши, соотношение 16:9
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые элементы:
|
||||||
|
- Студийное освещение (софтбоксы)
|
||||||
|
- Тёмный фон
|
||||||
|
- Fashion/editorial стиль
|
||||||
|
- Драматичное освещение
|
||||||
|
- Минималистичная одежда
|
||||||
|
- Фокус на модели
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Ретушь (retouch-cover.jpg)
|
||||||
|
|
||||||
|
### Промпт для Midjourney/DALL-E:
|
||||||
|
|
||||||
|
```
|
||||||
|
Split-screen before and after photo retouching comparison, left side: raw unedited portrait, right side: professionally retouched with perfect skin, cinematic color grading in warm vintage tones (#BF9B7A sand, #A64456 rose accents), elegant female portrait, soft studio lighting, premium beauty retouching showcase, subtle vintage film look, dark moody background, high-end magazine quality, photorealistic detail, 16:9 aspect ratio --ar 16:9 --style raw --q 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Русский промпт:
|
||||||
|
|
||||||
|
```
|
||||||
|
Сравнение до/после профессиональной ретуши фото в формате split-screen, левая сторона: необработанный портрет, правая сторона: профессионально отретушированный с идеальной кожей, кинематографичная цветокоррекция в тёплых винтажных тонах, элегантный женский портрет, мягкое студийное освещение, демонстрация премиальной beauty-ретуши, тонкий винтажный плёночный вид, тёмный мрачный фон, качество high-end журнала, фотореалистичные детали, соотношение 16:9
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые элементы:
|
||||||
|
- Split-screen (до/после)
|
||||||
|
- Демонстрация ретуши кожи
|
||||||
|
- Цветокоррекция
|
||||||
|
- Премиальное качество
|
||||||
|
- Тёплые тона
|
||||||
|
- Beauty-стиль
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Сертификат в подарок (certificate-cover.jpg)
|
||||||
|
|
||||||
|
### Промпт для Midjourney/DALL-E:
|
||||||
|
|
||||||
|
```
|
||||||
|
Elegant luxury gift certificate design mockup, vintage-style paper with ornate frame, warm color scheme (#BF9B7A sand, #A64456 vintage rose, #8C654F brown), professional photography gift voucher, minimalist elegant typography (Playfair Display serif), soft shadows on dark background (#1a1a1a), ribbon and wax seal details, premium stationery aesthetic, romantic vintage atmosphere, product photography style, 16:9 aspect ratio, photorealistic rendering --ar 16:9 --style raw --q 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Русский промпт:
|
||||||
|
|
||||||
|
```
|
||||||
|
Элегантный роскошный дизайн подарочного сертификата, винтажная бумага с орнаментальной рамкой, тёплая цветовая схема (песочный, винтажный розовый, коричневый), подарочный ваучер на профессиональную фотосессию, минималистичная элегантная типографика (serif шрифт Playfair Display), мягкие тени на тёмном фоне, детали с лентой и восковой печатью, премиальная канцелярская эстетика, романтичная винтажная атмосфера, стиль предметной фотографии, соотношение 16:9, фотореалистичный рендер
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые элементы:
|
||||||
|
- Сертификат/ваучер в кадре
|
||||||
|
- Винтажный дизайн
|
||||||
|
- Орнаменты и рамки
|
||||||
|
- Тёплые цвета палитры
|
||||||
|
- Элементы роскоши (лента, печать)
|
||||||
|
- Тёмный фон
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Общие рекомендации для всех промптов:
|
||||||
|
|
||||||
|
1. **Технические параметры:**
|
||||||
|
- Размер: 1920x1080px (16:9)
|
||||||
|
- Качество: высокое, фотореалистичное
|
||||||
|
- Формат: JPG
|
||||||
|
- Стиль: raw/professional photography
|
||||||
|
|
||||||
|
2. **Цветокоррекция:**
|
||||||
|
- Обязательно использовать тёплые винтажные тона
|
||||||
|
- Приглушенная палитра, не яркие цвета
|
||||||
|
- Акценты: #A64456 (розовый), #BF9B7A (песочный)
|
||||||
|
- Тени: тёмные, насыщенные (#1a1a1a, #594336)
|
||||||
|
|
||||||
|
3. **Настроение:**
|
||||||
|
- Кинематографичность
|
||||||
|
- Элегантность и роскошь
|
||||||
|
- Винтажная эстетика
|
||||||
|
- Тёмная, мрачная атмосфера (но не депрессивная)
|
||||||
|
- Романтика и мечтательность
|
||||||
|
|
||||||
|
4. **Освещение:**
|
||||||
|
- Драматичное, но мягкое
|
||||||
|
- Золотой час для уличных
|
||||||
|
- Студийное для портретов
|
||||||
|
- Тёплые оттенки света
|
||||||
|
|
||||||
|
5. **Постобработка:**
|
||||||
|
- Эффект плёнки (grain)
|
||||||
|
- Малая глубина резкости (bokeh)
|
||||||
|
- Виньетирование
|
||||||
|
- Профессиональная цветокоррекция
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Альтернативные сервисы для генерации:
|
||||||
|
|
||||||
|
- **Midjourney** (рекомендуется) - лучшая фотореалистичность
|
||||||
|
- **DALL-E 3** - хорошее понимание промптов
|
||||||
|
- **Stable Diffusion** (модели: Realistic Vision, DreamShaper)
|
||||||
|
- **Leonardo.ai** - для быстрых итераций
|
||||||
|
- **Firefly Adobe** - интеграция с Adobe
|
||||||
|
|
||||||
|
## Дополнительные ключевые слова для улучшения результата:
|
||||||
|
|
||||||
|
```
|
||||||
|
cinematic, film grain, vintage aesthetic, warm tones, moody, atmospheric, professional photography, editorial style, soft focus, bokeh, golden hour, dramatic lighting, minimalist, elegant, sophisticated, luxury, romantic, dreamy, photorealistic, high-end, premium quality
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Важно:** После генерации обязательно:
|
||||||
|
1. Проверить соответствие цветовой палитре сайта
|
||||||
|
2. При необходимости скорректировать цвета в Lightroom/Photoshop
|
||||||
|
3. Добавить film grain для винтажности
|
||||||
|
4. Оптимизировать размер файла (<500KB)
|
||||||
|
5. Убедиться в едином стиле всех обложек
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
REQUIRED IMAGES FOR ROMANOVNA PHOTOGRAPHY WEBSITE
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
IMPORTANT: Add the following images for the site to work correctly
|
||||||
|
|
||||||
|
1. LOGO
|
||||||
|
Path: /public/logo/romanovna-logo.png
|
||||||
|
Size: 200x200px minimum
|
||||||
|
Format: PNG with transparent background
|
||||||
|
|
||||||
|
2. CAROUSEL WORKS (6 images)
|
||||||
|
Path: /public/images/carousel/
|
||||||
|
Files: work-1.jpg, work-2.jpg, work-3.jpg, work-4.jpg, work-5.jpg, work-6.jpg
|
||||||
|
Size: 1920x1200px
|
||||||
|
|
||||||
|
3. SERVICES - STREET PHOTOSHOOT
|
||||||
|
Path: /public/images/services/
|
||||||
|
- street-cover.jpg (1600x1000px)
|
||||||
|
- street-1.jpg, street-2.jpg, street-3.jpg (800x1000px)
|
||||||
|
|
||||||
|
4. SERVICES - STUDIO PHOTOSHOOT
|
||||||
|
- studio-cover.jpg (1600x1000px)
|
||||||
|
- studio-1.jpg, studio-2.jpg, studio-3.jpg (800x1000px)
|
||||||
|
|
||||||
|
5. SERVICES - RETOUCH
|
||||||
|
- retouch-cover.jpg (1600x1000px)
|
||||||
|
- retouch-1.jpg, retouch-2.jpg (800x1000px)
|
||||||
|
|
||||||
|
6. SERVICES - GIFT CERTIFICATE
|
||||||
|
- certificate-cover.jpg (1600x1000px)
|
||||||
|
- certificate-1.jpg (800x1000px)
|
||||||
|
|
||||||
|
Without these images, the site will display errors or empty blocks.
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
=================================================================
|
||||||
|
БЫСТРЫЕ ПРОМПТЫ ДЛЯ КОПИРОВАНИЯ (Romanovna Photography)
|
||||||
|
=================================================================
|
||||||
|
|
||||||
|
Общий стиль: Тёмная винтажная эстетика, кинематографичность
|
||||||
|
Цвета: #A64456 (розовый), #BF9B7A (песочный), #8C654F (коричневый)
|
||||||
|
Размер: 1920x1080px (16:9)
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
1. ФОТОСЕССИЯ НА УЛИЦЕ (street-cover.jpg)
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Cinematic street photography scene, elegant young woman in vintage outfit walking on cobblestone European street at golden hour, warm sunset light, bokeh background with old architecture, film grain texture, muted color grading with warm tones (sand #BF9B7A, vintage brown #8C654F), atmospheric haze, shot on film camera aesthetic, vintage rose accents #A64456, professional fashion photography, dreamy and romantic mood, shallow depth of field, 16:9 aspect ratio, photorealistic --ar 16:9 --style raw --stylize 300
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
2. ФОТОСЕССИЯ В СТУДИИ (studio-cover.jpg)
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Professional studio portrait photography, elegant fashion model in minimalist vintage-style clothing, dramatic studio lighting setup with softbox and rim light, dark moody background (#1a1a1a), warm accent lighting (#A64456 rose tint), high-end fashion editorial style, sharp focus on face, sophisticated pose, muted warm color palette (sand #BF9B7A, vintage brown tones), cinematic atmosphere, beauty dish lighting, professional retouching aesthetic, 16:9 aspect ratio --ar 16:9 --style raw --q 2
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
3. РЕТУШЬ (retouch-cover.jpg)
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Split-screen before and after photo retouching comparison, left side: raw unedited portrait, right side: professionally retouched with perfect skin, cinematic color grading in warm vintage tones (#BF9B7A sand, #A64456 rose accents), elegant female portrait, soft studio lighting, premium beauty retouching showcase, subtle vintage film look, dark moody background, high-end magazine quality, photorealistic detail, 16:9 aspect ratio --ar 16:9 --style raw --q 2
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
4. СЕРТИФИКАТ В ПОДАРОК (certificate-cover.jpg)
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Elegant luxury gift certificate design mockup, vintage-style paper with ornate frame, warm color scheme (#BF9B7A sand, #A64456 vintage rose, #8C654F brown), professional photography gift voucher, minimalist elegant typography (Playfair Display serif), soft shadows on dark background (#1a1a1a), ribbon and wax seal details, premium stationery aesthetic, romantic vintage atmosphere, product photography style, 16:9 aspect ratio, photorealistic rendering --ar 16:9 --style raw --q 2
|
||||||
|
|
||||||
|
=================================================================
|
||||||
|
ВАЖНО ПОСЛЕ ГЕНЕРАЦИИ:
|
||||||
|
=================================================================
|
||||||
|
1. Проверить соответствие цветовой палитре (#A64456, #BF9B7A, #8C654F)
|
||||||
|
2. Добавить film grain для винтажности
|
||||||
|
3. Скорректировать цвета если нужно (Lightroom/Photoshop)
|
||||||
|
4. Оптимизировать до <500KB
|
||||||
|
5. Размер: 1920x1080px
|
||||||
|
|
||||||
|
СЕРВИСЫ:
|
||||||
|
- Midjourney (лучший результат)
|
||||||
|
- DALL-E 3
|
||||||
|
- Leonardo.ai
|
||||||
|
- Stable Diffusion
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Изображения для сайта
|
||||||
|
|
||||||
|
## Карусель работ
|
||||||
|
|
||||||
|
Добавьте 6 изображений в папку `carousel/`:
|
||||||
|
- `work-1.jpg`
|
||||||
|
- `work-2.jpg`
|
||||||
|
- `work-3.jpg`
|
||||||
|
- `work-4.jpg`
|
||||||
|
- `work-5.jpg`
|
||||||
|
- `work-6.jpg`
|
||||||
|
|
||||||
|
**Рекомендации:**
|
||||||
|
- Формат: JPG или WebP
|
||||||
|
- Размер: минимум 1920x1080px (16:9)
|
||||||
|
- Оптимизация: сжатие для веба
|
||||||
|
|
||||||
|
## Услуги
|
||||||
|
|
||||||
|
### Фотосессия на улице (street)
|
||||||
|
- `street-cover.jpg` - обложка (1920x1080px)
|
||||||
|
- `street-example-1.jpg` - пример работы
|
||||||
|
- `street-example-2.jpg` - пример работы
|
||||||
|
- `street-example-3.jpg` - пример работы
|
||||||
|
|
||||||
|
### Фотосессия в студии (studio)
|
||||||
|
- `studio-cover.jpg` - обложка (1920x1080px)
|
||||||
|
- `studio-example-1.jpg` - пример работы
|
||||||
|
- `studio-example-2.jpg` - пример работы
|
||||||
|
|
||||||
|
### Ретушь (retouch)
|
||||||
|
- `retouch-cover.jpg` - обложка (1920x1080px)
|
||||||
|
- `retouch-example-1.jpg` - пример работы (до/после)
|
||||||
|
- `retouch-example-2.jpg` - пример работы (до/после)
|
||||||
|
|
||||||
|
### Сертификат в подарок (certificate)
|
||||||
|
- `certificate-cover.jpg` - обложка (1920x1080px)
|
||||||
|
- `certificate-example-1.jpg` - пример сертификата
|
||||||
|
|
||||||
|
## Оптимизация изображений
|
||||||
|
|
||||||
|
Рекомендуется использовать инструменты для оптимизации:
|
||||||
|
- [Squoosh](https://squoosh.app/) - онлайн оптимизатор
|
||||||
|
- [ImageOptim](https://imageoptim.com/) - для Mac
|
||||||
|
- [TinyPNG](https://tinypng.com/) - онлайн сервис
|
||||||
|
|
||||||
|
**Целевой размер:** каждое изображение должно быть < 500KB после оптимизации.
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 435 KiB |
|
After Width: | Height: | Size: 494 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 698 KiB |
|
After Width: | Height: | Size: 709 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 368 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 812 KiB |
|
|
@ -0,0 +1,12 @@
|
||||||
|
import ContactInfo from '@/components/contacts/ContactInfo';
|
||||||
|
import YandexMap from '@/components/contacts/YandexMap';
|
||||||
|
|
||||||
|
export default function ContactsPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContactInfo />
|
||||||
|
<YandexMap />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Inter:wght@300;400;500;600&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Цветовая палитра */
|
||||||
|
--color-accent: #A64456;
|
||||||
|
--color-sand: #BF9B7A;
|
||||||
|
--color-light-vintage: #D9B79A;
|
||||||
|
--color-brown-vintage: #8C654F;
|
||||||
|
--color-dark: #594336;
|
||||||
|
--color-bg-dark: #1a1a1a;
|
||||||
|
--color-bg-secondary: #2a2a2a;
|
||||||
|
--color-text-primary: #f5f5f5;
|
||||||
|
--color-text-secondary: #d0d0d0;
|
||||||
|
--color-text-muted: #a0a0a0;
|
||||||
|
|
||||||
|
/* Типографика */
|
||||||
|
--font-heading: 'Playfair Display', serif;
|
||||||
|
--font-body: 'Inter', sans-serif;
|
||||||
|
|
||||||
|
/* Отступы */
|
||||||
|
--spacing-xs: 0.5rem;
|
||||||
|
--spacing-sm: 1rem;
|
||||||
|
--spacing-md: 2rem;
|
||||||
|
--spacing-lg: 3rem;
|
||||||
|
--spacing-xl: 4rem;
|
||||||
|
|
||||||
|
/* Переходы */
|
||||||
|
--transition-base: 0.3s ease;
|
||||||
|
--transition-slow: 0.5s ease;
|
||||||
|
|
||||||
|
/* Тени */
|
||||||
|
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Винтажная текстура (опционально) */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle at 20% 50%, rgba(217, 183, 154, 0.03) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 80%, rgba(191, 155, 122, 0.02) 0%, transparent 50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Утилиты */
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 var(--spacing-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Плавные переходы для страниц */
|
||||||
|
.page-transition {
|
||||||
|
animation: fadeIn var(--transition-slow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { Inter, Playfair_Display } from 'next/font/google';
|
||||||
|
import './globals.css';
|
||||||
|
import Header from '@/components/layout/Header';
|
||||||
|
import Footer from '@/components/layout/Footer';
|
||||||
|
import { companyInfo } from '@/lib/company';
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin', 'cyrillic'],
|
||||||
|
variable: '--font-inter',
|
||||||
|
display: 'swap',
|
||||||
|
});
|
||||||
|
|
||||||
|
const playfair = Playfair_Display({
|
||||||
|
subsets: ['latin', 'cyrillic'],
|
||||||
|
variable: '--font-playfair',
|
||||||
|
display: 'swap',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: `${companyInfo.name} - ${companyInfo.photographerName}`,
|
||||||
|
description: `${companyInfo.slogan}. Профессиональная фотосъёмка: фотосессии на улице и в студии, ретушь, подарочные сертификаты.`,
|
||||||
|
keywords: 'фотограф, фотосессия, портрет, ретушь, фотостудия',
|
||||||
|
icons: {
|
||||||
|
icon: companyInfo.logo,
|
||||||
|
shortcut: companyInfo.logo,
|
||||||
|
apple: companyInfo.logo,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="ru" className={`${inter.variable} ${playfair.variable}`}>
|
||||||
|
<body>
|
||||||
|
<Header />
|
||||||
|
<main className="page-transition">{children}</main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
.notFound {
|
||||||
|
min-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(4rem, 8vw, 8rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-accent);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.875rem 2rem;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
background-color: #b84d5f;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import Link from 'next/link';
|
||||||
|
import LayoutContainer from '@/components/layout/LayoutContainer';
|
||||||
|
import styles from './not-found.module.css';
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<div className={styles.notFound}>
|
||||||
|
<LayoutContainer>
|
||||||
|
<h1 className={styles.title}>404</h1>
|
||||||
|
<p className={styles.text}>Страница не найдена</p>
|
||||||
|
<Link href="/" className={styles.link}>
|
||||||
|
Вернуться на главную
|
||||||
|
</Link>
|
||||||
|
</LayoutContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import HeroSection from '@/components/home/HeroSection';
|
||||||
|
import WorksCarousel from '@/components/home/WorksCarousel';
|
||||||
|
import ServicesGrid from '@/components/home/ServicesGrid';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeroSection />
|
||||||
|
<WorksCarousel />
|
||||||
|
<ServicesGrid />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { getServiceBySlug } from '@/lib/services';
|
||||||
|
import ServiceDetails from '@/components/services/ServiceDetails';
|
||||||
|
|
||||||
|
interface ServicePageProps {
|
||||||
|
params: {
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ServicePage({ params }: ServicePageProps) {
|
||||||
|
const service = getServiceBySlug(params.slug);
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ServiceDetails service={service} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return [
|
||||||
|
{ slug: 'street' },
|
||||||
|
{ slug: 'studio' },
|
||||||
|
{ slug: 'retouch' },
|
||||||
|
{ slug: 'certificate' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
.servicesPage {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition-base), box-shadow var(--transition-base);
|
||||||
|
border: 1px solid rgba(166, 68, 86, 0.2);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageWrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform var(--transition-slow);
|
||||||
|
filter: brightness(0.8) contrast(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .image {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
rgba(26, 26, 26, 0.6)
|
||||||
|
);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardDescription {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkText {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .linkText {
|
||||||
|
color: var(--color-light-vintage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.servicesPage {
|
||||||
|
padding: var(--spacing-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { services } from '@/lib/services';
|
||||||
|
import LayoutContainer from '@/components/layout/LayoutContainer';
|
||||||
|
import styles from './page.module.css';
|
||||||
|
|
||||||
|
export default function ServicesPage() {
|
||||||
|
return (
|
||||||
|
<div className={styles.servicesPage}>
|
||||||
|
<LayoutContainer>
|
||||||
|
<h1 className={styles.title}>Услуги</h1>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{services.map((service) => (
|
||||||
|
<Link
|
||||||
|
key={service.slug}
|
||||||
|
href={`/services/${service.slug}`}
|
||||||
|
className={styles.card}
|
||||||
|
>
|
||||||
|
<div className={styles.imageWrapper}>
|
||||||
|
<Image
|
||||||
|
src={service.coverImage}
|
||||||
|
alt={service.title}
|
||||||
|
fill
|
||||||
|
className={styles.image}
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<div className={styles.overlay} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<h2 className={styles.cardTitle}>{service.title}</h2>
|
||||||
|
<p className={styles.cardDescription}>{service.description}</p>
|
||||||
|
<span className={styles.linkText}>Подробнее →</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</LayoutContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
.contactInfo {
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramBlock {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramText {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramLink {
|
||||||
|
color: var(--color-accent);
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramLink:hover {
|
||||||
|
color: var(--color-light-vintage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.contactInfo {
|
||||||
|
padding: var(--spacing-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0 var(--spacing-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { companyInfo } from '@/lib/company';
|
||||||
|
import LayoutContainer from '@/components/layout/LayoutContainer';
|
||||||
|
import InstagramLink from '@/components/ui/InstagramLink';
|
||||||
|
import styles from './ContactInfo.module.css';
|
||||||
|
|
||||||
|
export default function ContactInfo() {
|
||||||
|
const instagramUrl = `https://instagram.com/${companyInfo.instagram}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.contactInfo}>
|
||||||
|
<LayoutContainer>
|
||||||
|
<h1 className={styles.title}>Контакты</h1>
|
||||||
|
<p className={styles.text}>
|
||||||
|
Вы всегда можете написать мне в Instagram
|
||||||
|
</p>
|
||||||
|
<div className={styles.instagramBlock}>
|
||||||
|
<p className={styles.instagramText}>
|
||||||
|
Instagram:{' '}
|
||||||
|
<a
|
||||||
|
href={instagramUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={styles.instagramLink}
|
||||||
|
>
|
||||||
|
@{companyInfo.instagram}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<InstagramLink>Перейти в Instagram</InstagramLink>
|
||||||
|
</div>
|
||||||
|
</LayoutContainer>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
.mapContainer {
|
||||||
|
width: 100%;
|
||||||
|
margin: var(--spacing-xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border: 1px solid rgba(166, 68, 86, 0.2);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.map {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import styles from './YandexMap.module.css';
|
||||||
|
|
||||||
|
export default function YandexMap() {
|
||||||
|
const mapRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scriptLoadedRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mapRef.current || scriptLoadedRef.current) return;
|
||||||
|
|
||||||
|
// Очистка предыдущего содержимого
|
||||||
|
mapRef.current.innerHTML = '';
|
||||||
|
|
||||||
|
// Создание скрипта конструктора карт
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.charset = 'utf-8';
|
||||||
|
script.async = true;
|
||||||
|
script.src =
|
||||||
|
'https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3Ade579bce5d153a77c904d2d713e051548ffef8ef516c10078f5ac83e53d5872a&width=1200&height=500&lang=ru_RU&scroll=true';
|
||||||
|
|
||||||
|
// Добавляем скрипт в контейнер карты (конструктор создаст iframe в этом контейнере)
|
||||||
|
mapRef.current.appendChild(script);
|
||||||
|
scriptLoadedRef.current = true;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Очистка при размонтировании
|
||||||
|
if (mapRef.current) {
|
||||||
|
mapRef.current.innerHTML = '';
|
||||||
|
scriptLoadedRef.current = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.mapContainer}>
|
||||||
|
<div ref={mapRef} className={styles.map} id="yandex-map" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
min-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--color-bg-dark) 0%,
|
||||||
|
var(--color-dark) 50%,
|
||||||
|
var(--color-bg-dark) 100%
|
||||||
|
);
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle at 30% 40%, rgba(166, 68, 86, 0.1) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 70% 60%, rgba(191, 155, 122, 0.08) 0%, transparent 50%);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: var(--spacing-xl) var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:
|
||||||
|
linear-gradient(to bottom, rgba(26, 26, 26, 0.7), rgba(26, 26, 26, 0.5)),
|
||||||
|
linear-gradient(to top, rgba(26, 26, 26, 0.7), rgba(26, 26, 26, 0.5));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 800px;
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2.5rem, 5vw, 4.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.texture {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 120%;
|
||||||
|
height: 120%;
|
||||||
|
background-image:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 2px,
|
||||||
|
rgba(217, 183, 154, 0.03) 2px,
|
||||||
|
rgba(217, 183, 154, 0.03) 4px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero {
|
||||||
|
min-height: 50vh;
|
||||||
|
padding: var(--spacing-lg) var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { companyInfo } from '@/lib/company';
|
||||||
|
import styles from './HeroSection.module.css';
|
||||||
|
|
||||||
|
export default function HeroSection() {
|
||||||
|
return (
|
||||||
|
<section className={styles.hero}>
|
||||||
|
<div className={styles.overlay} />
|
||||||
|
<div className={styles.content}>
|
||||||
|
<h1 className={styles.slogan}>{companyInfo.slogan}</h1>
|
||||||
|
<div className={styles.texture} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
.servicesSection {
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
var(--color-bg-dark),
|
||||||
|
var(--color-bg-secondary)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2rem, 4vw, 3rem);
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition-base), box-shadow var(--transition-base);
|
||||||
|
border: 1px solid rgba(166, 68, 86, 0.2);
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageWrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 70%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform var(--transition-slow);
|
||||||
|
filter: brightness(0.8) contrast(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .image {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
rgba(26, 26, 26, 0.7)
|
||||||
|
);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
z-index: 2;
|
||||||
|
background: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgba(26, 26, 26, 0.95),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkText {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .linkText {
|
||||||
|
color: var(--color-light-vintage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { services } from '@/lib/services';
|
||||||
|
import LayoutContainer from '@/components/layout/LayoutContainer';
|
||||||
|
import styles from './ServicesGrid.module.css';
|
||||||
|
|
||||||
|
export default function ServicesGrid() {
|
||||||
|
return (
|
||||||
|
<section className={styles.servicesSection}>
|
||||||
|
<LayoutContainer>
|
||||||
|
<h2 className={styles.title}>Услуги</h2>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{services.map((service) => (
|
||||||
|
<Link
|
||||||
|
key={service.slug}
|
||||||
|
href={`/services/${service.slug}`}
|
||||||
|
className={styles.card}
|
||||||
|
>
|
||||||
|
<div className={styles.imageWrapper}>
|
||||||
|
<Image
|
||||||
|
src={service.coverImage}
|
||||||
|
alt={service.title}
|
||||||
|
fill
|
||||||
|
className={styles.image}
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<div className={styles.overlay} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<h3 className={styles.cardTitle}>{service.title}</h3>
|
||||||
|
<span className={styles.linkText}>Подробнее →</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</LayoutContainer>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
.carouselSection {
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2rem, 4vw, 3rem);
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px; /* Уменьшена ширина для вертикальных фото */
|
||||||
|
margin: 0 auto;
|
||||||
|
aspect-ratio: 3 / 4; /* Вертикальные фотографии */
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slidesContainer {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity var(--transition-slow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide.active {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
object-fit: cover;
|
||||||
|
filter: brightness(0.95) contrast(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: rgba(26, 26, 26, 0.7);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 2px solid var(--color-accent);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all var(--transition-base);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow:hover {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
transform: translateY(-50%) scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow:first-of-type {
|
||||||
|
left: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow:last-of-type {
|
||||||
|
right: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dots {
|
||||||
|
position: absolute;
|
||||||
|
bottom: var(--spacing-sm);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--color-text-primary);
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.active {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.carouselSection {
|
||||||
|
padding: var(--spacing-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel {
|
||||||
|
aspect-ratio: 3 / 4; /* Сохраняем вертикальное соотношение */
|
||||||
|
max-width: 90%; /* Адаптивная ширина на мобильных */
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow:first-of-type {
|
||||||
|
left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow:last-of-type {
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { galleryImages } from '@/lib/gallery';
|
||||||
|
import LayoutContainer from '@/components/layout/LayoutContainer';
|
||||||
|
import styles from './WorksCarousel.module.css';
|
||||||
|
|
||||||
|
export default function WorksCarousel() {
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [touchStart, setTouchStart] = useState(0);
|
||||||
|
const [touchEnd, setTouchEnd] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCurrentIndex((prev) => (prev + 1) % galleryImages.length);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const goToSlide = (index: number) => {
|
||||||
|
setCurrentIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToPrevious = () => {
|
||||||
|
setCurrentIndex((prev) =>
|
||||||
|
prev === 0 ? galleryImages.length - 1 : prev - 1
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToNext = () => {
|
||||||
|
setCurrentIndex((prev) => (prev + 1) % galleryImages.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchStart = (e: React.TouchEvent) => {
|
||||||
|
setTouchStart(e.targetTouches[0].clientX);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchMove = (e: React.TouchEvent) => {
|
||||||
|
setTouchEnd(e.targetTouches[0].clientX);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
if (!touchStart || !touchEnd) return;
|
||||||
|
const distance = touchStart - touchEnd;
|
||||||
|
const isLeftSwipe = distance > 50;
|
||||||
|
const isRightSwipe = distance < -50;
|
||||||
|
|
||||||
|
if (isLeftSwipe) {
|
||||||
|
goToNext();
|
||||||
|
}
|
||||||
|
if (isRightSwipe) {
|
||||||
|
goToPrevious();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.carouselSection}>
|
||||||
|
<LayoutContainer>
|
||||||
|
<h2 className={styles.title}>Мои работы</h2>
|
||||||
|
<div
|
||||||
|
className={styles.carousel}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={styles.arrow}
|
||||||
|
onClick={goToPrevious}
|
||||||
|
aria-label="Предыдущее изображение"
|
||||||
|
>
|
||||||
|
‹
|
||||||
|
</button>
|
||||||
|
<div className={styles.slidesContainer}>
|
||||||
|
{galleryImages.map((image, index) => (
|
||||||
|
<div
|
||||||
|
key={image.id}
|
||||||
|
className={`${styles.slide} ${
|
||||||
|
index === currentIndex ? styles.active : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={image.src}
|
||||||
|
alt={image.alt}
|
||||||
|
fill
|
||||||
|
className={styles.image}
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 90vw, 1200px"
|
||||||
|
loading={index === currentIndex ? 'eager' : 'lazy'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={styles.arrow}
|
||||||
|
onClick={goToNext}
|
||||||
|
aria-label="Следующее изображение"
|
||||||
|
>
|
||||||
|
›
|
||||||
|
</button>
|
||||||
|
<div className={styles.dots}>
|
||||||
|
{galleryImages.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={`${styles.dot} ${
|
||||||
|
index === currentIndex ? styles.active : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => goToSlide(index)}
|
||||||
|
aria-label={`Перейти к слайду ${index + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LayoutContainer>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
.footer {
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
border-top: 1px solid rgba(166, 68, 86, 0.2);
|
||||||
|
padding: var(--spacing-lg) 0;
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagram {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramLink {
|
||||||
|
color: var(--color-accent);
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagramLink:hover {
|
||||||
|
color: var(--color-light-vintage);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.footer {
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { companyInfo } from '@/lib/company';
|
||||||
|
import InstagramLink from '@/components/ui/InstagramLink';
|
||||||
|
import styles from './Footer.module.css';
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const instagramUrl = `https://instagram.com/${companyInfo.instagram}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<p className={styles.instagram}>
|
||||||
|
Instagram:{' '}
|
||||||
|
<Link
|
||||||
|
href={instagramUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={styles.instagramLink}
|
||||||
|
>
|
||||||
|
@{companyInfo.instagram}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
<InstagramLink variant="outline" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.copyright}>
|
||||||
|
<p>© {currentYear} {companyInfo.name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
.header {
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
border-bottom: 1px solid rgba(166, 68, 86, 0.2);
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
transition: opacity var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoText {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: color var(--transition-base);
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
transition: width var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink:hover {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink:hover::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoText {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.logoText {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { companyInfo } from '@/lib/company';
|
||||||
|
import styles from './Header.module.css';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Link href="/" className={styles.logo}>
|
||||||
|
<Image
|
||||||
|
src={companyInfo.logo}
|
||||||
|
alt={companyInfo.name}
|
||||||
|
width={60}
|
||||||
|
height={60}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<span className={styles.logoText}>
|
||||||
|
{companyInfo.photographerName}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<nav className={styles.nav}>
|
||||||
|
<Link href="/" className={styles.navLink}>
|
||||||
|
Главная
|
||||||
|
</Link>
|
||||||
|
<Link href="/services" className={styles.navLink}>
|
||||||
|
Услуги
|
||||||
|
</Link>
|
||||||
|
<Link href="/contacts" className={styles.navLink}>
|
||||||
|
Контакты
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 var(--spacing-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import styles from './LayoutContainer.module.css';
|
||||||
|
|
||||||
|
interface LayoutContainerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LayoutContainer({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
}: LayoutContainerProps) {
|
||||||
|
return (
|
||||||
|
<div className={`${styles.container} ${className}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
.servicePage {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 50vh;
|
||||||
|
min-height: 400px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroImage {
|
||||||
|
object-fit: cover;
|
||||||
|
filter: brightness(0.6) contrast(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(26, 26, 26, 0.7),
|
||||||
|
rgba(26, 26, 26, 0.9)
|
||||||
|
);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: var(--spacing-xl) var(--spacing-md);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroTitle {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.7);
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto var(--spacing-xl);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.examples {
|
||||||
|
margin: var(--spacing-xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.examplesTitle {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(1.75rem, 3vw, 2.5rem);
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.examplesGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleItem {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 3 / 4; /* Вертикальные фотографии */
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border: 1px solid rgba(166, 68, 86, 0.2);
|
||||||
|
transition: transform var(--transition-base), box-shadow var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleItem:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleImage {
|
||||||
|
object-fit: cover;
|
||||||
|
filter: brightness(0.9) contrast(1.05);
|
||||||
|
transition: transform var(--transition-slow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleItem:hover .exampleImage {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
padding: var(--spacing-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero {
|
||||||
|
height: 40vh;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
padding: var(--spacing-lg) var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0 var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.examplesGrid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleItem {
|
||||||
|
aspect-ratio: 3 / 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.examplesGrid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { Service } from '@/lib/types';
|
||||||
|
import LayoutContainer from '@/components/layout/LayoutContainer';
|
||||||
|
import InstagramLink from '@/components/ui/InstagramLink';
|
||||||
|
import styles from './ServiceDetails.module.css';
|
||||||
|
|
||||||
|
interface ServiceDetailsProps {
|
||||||
|
service: Service;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ServiceDetails({ service }: ServiceDetailsProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.servicePage}>
|
||||||
|
<div className={styles.hero}>
|
||||||
|
<Image
|
||||||
|
src={service.coverImage}
|
||||||
|
alt={service.title}
|
||||||
|
fill
|
||||||
|
className={styles.heroImage}
|
||||||
|
priority
|
||||||
|
sizes="100vw"
|
||||||
|
/>
|
||||||
|
<div className={styles.heroOverlay} />
|
||||||
|
<div className={styles.heroContent}>
|
||||||
|
<h1 className={styles.heroTitle}>{service.title}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LayoutContainer>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<p className={styles.description}>{service.description}</p>
|
||||||
|
|
||||||
|
{service.examples.length > 0 && (
|
||||||
|
<section className={styles.examples}>
|
||||||
|
<h2 className={styles.examplesTitle}>Примеры работ</h2>
|
||||||
|
<div className={styles.examplesGrid}>
|
||||||
|
{service.examples.map((example, index) => (
|
||||||
|
<div key={index} className={styles.exampleItem}>
|
||||||
|
<Image
|
||||||
|
src={example}
|
||||||
|
alt={`Пример работы ${index + 1}`}
|
||||||
|
fill
|
||||||
|
className={styles.exampleImage}
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.cta}>
|
||||||
|
<InstagramLink>Связаться в Instagram</InstagramLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LayoutContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.875rem 2rem;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-base);
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.1),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary:hover {
|
||||||
|
background-color: #b84d5f;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background-color: var(--color-sand);
|
||||||
|
color: var(--color-bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary:hover {
|
||||||
|
background-color: var(--color-light-vintage);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 2px solid var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline:hover {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './Button.module.css';
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
href?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline';
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Button({
|
||||||
|
children,
|
||||||
|
href,
|
||||||
|
onClick,
|
||||||
|
variant = 'primary',
|
||||||
|
className = '',
|
||||||
|
}: ButtonProps) {
|
||||||
|
const classes = `${styles.button} ${styles[variant]} ${className}`;
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<Link href={href} className={classes}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={classes} onClick={onClick}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { companyInfo } from '@/lib/company';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
|
interface InstagramLinkProps {
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline';
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InstagramLink({
|
||||||
|
variant = 'primary',
|
||||||
|
children,
|
||||||
|
}: InstagramLinkProps) {
|
||||||
|
const instagramUrl = `https://instagram.com/${companyInfo.instagram}`;
|
||||||
|
const text = children || `Написать в Instagram`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
href={instagramUrl}
|
||||||
|
variant={variant}
|
||||||
|
className="instagram-link"
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { CompanyInfo } from './types';
|
||||||
|
|
||||||
|
export const companyInfo: CompanyInfo = {
|
||||||
|
name: 'Romanovna Photo',
|
||||||
|
photographerName: 'Ангелина Чёрная',
|
||||||
|
logo: '/logo/romanovna-logo.png',
|
||||||
|
instagram: 'romanovnaph_ch',
|
||||||
|
slogan: 'Эстетика в каждом кадре',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { GalleryImage } from './types';
|
||||||
|
|
||||||
|
// Карусель использует фото из примеров услуг для оптимизации
|
||||||
|
export const galleryImages: GalleryImage[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
src: '/images/services/street-example-1.jpg',
|
||||||
|
alt: 'Уличная фотосессия',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
src: '/images/services/studio-example-1.jpg',
|
||||||
|
alt: 'Студийная фотосессия',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
src: '/images/services/street-example-2.jpg',
|
||||||
|
alt: 'Уличная фотосессия',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
src: '/images/services/studio-example-2.jpg',
|
||||||
|
alt: 'Студийная фотосессия',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
src: '/images/services/street-example-3.jpg',
|
||||||
|
alt: 'Уличная фотосессия',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
src: '/images/services/studio-example-3.jpg',
|
||||||
|
alt: 'Студийная фотосессия',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Service } from './types';
|
||||||
|
|
||||||
|
export const services: Service[] = [
|
||||||
|
{
|
||||||
|
slug: 'street',
|
||||||
|
title: 'Фотосессия на улице',
|
||||||
|
description: 'Живые эмоции и естественные кадры на фоне городских пейзажей, парков и архитектуры. Идеально для портретов, семейных фотосессий и романтических съёмок.',
|
||||||
|
coverImage: '/images/services/street-cover.jpg',
|
||||||
|
examples: [
|
||||||
|
'/images/services/street-example-1.jpg',
|
||||||
|
'/images/services/street-example-2.jpg',
|
||||||
|
'/images/services/street-example-3.jpg',
|
||||||
|
'/images/services/street-example-4.jpg',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'studio',
|
||||||
|
title: 'Фотосессия в студии',
|
||||||
|
description: 'Профессиональная студийная съёмка с качественным светом и оборудованием. Подходит для портретов, fashion-съёмок и коммерческих проектов.',
|
||||||
|
coverImage: '/images/services/studio-cover.jpg',
|
||||||
|
examples: [
|
||||||
|
'/images/services/studio-example-1.jpg',
|
||||||
|
'/images/services/studio-example-2.jpg',
|
||||||
|
'/images/services/studio-example-3.jpg',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'retouch',
|
||||||
|
title: 'Ретушь',
|
||||||
|
description: 'Профессиональная обработка фотографий: цветокоррекция, ретушь кожи, художественная обработка. Придание вашим снимкам премиального вида.',
|
||||||
|
coverImage: '/images/services/retouch-cover.jpg',
|
||||||
|
examples: [
|
||||||
|
'/images/services/retouch-example-1.jpg',
|
||||||
|
'/images/services/retouch-example-2.jpg',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'certificate',
|
||||||
|
title: 'Сертификат в подарок',
|
||||||
|
description: 'Подарочный сертификат на фотосессию — идеальный подарок для близких. Выберите сумму и тип съёмки, а получатель сам решит, когда использовать сертификат.',
|
||||||
|
coverImage: '/images/services/certificate-cover.jpg',
|
||||||
|
examples: [
|
||||||
|
'/images/services/certificate-example-1.jpg',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getServiceBySlug(slug: string): Service | undefined {
|
||||||
|
return services.find((service) => service.slug === slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
export interface Service {
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
coverImage: string;
|
||||||
|
examples: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GalleryImage {
|
||||||
|
id: string;
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanyInfo {
|
||||||
|
name: string;
|
||||||
|
photographerName: string;
|
||||||
|
logo: string;
|
||||||
|
instagram: string;
|
||||||
|
slogan: string;
|
||||||
|
address?: string;
|
||||||
|
phone?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", "old", "src_old"]
|
||||||
|
}
|
||||||
|
|
||||||