Full Apple-style app with backend API, admin panel, scroll animations
This commit is contained in:
parent
d1848333cc
commit
9ae6383d9d
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.env
|
||||
node_modules/
|
||||
.vibe42-run.log
|
||||
.vibe42-run.pid
|
||||
*.local
|
||||
14
AGENTS.md
14
AGENTS.md
@ -11,7 +11,7 @@ Workspace юзера `temp`. Это **учебная среда**, где обы
|
||||
1. **работающим воплощением ЕГО идеи**: сайт — опубликован на `https://pages.git.vibe42.kz/temp/<repo>/`, бот/приложение — запущены через `run` с живой ссылкой,
|
||||
2. ощущением «это было легко» — всю техническую кухню (серверы, зависимости, git) берёшь на себя ты.
|
||||
|
||||
Юзер не разработчик. Ему важен **работающий результат**, а не код.
|
||||
Юзер не разработчик. Ему важен **работающий результат**, и оптимизированный и эффективный код.
|
||||
|
||||
---
|
||||
|
||||
@ -23,7 +23,7 @@ Workspace юзера `temp`. Это **учебная среда**, где обы
|
||||
|
||||
| Что хочет юзер | Стек (ЖЁСТКО) | Рецепт |
|
||||
|----------------|---------------|--------|
|
||||
| Веб сайт фронтенд на javascript | **Статика:** `index.html` + `style.css` + `script.js` + еще файлы для фронтенда, ванильный JS. БЕЗ сборки, БЕЗ React/Vue/Vite, БЕЗ npm. Tailwind или bootstrap — только CDN. | старт с `design.md`, публикация в `pages` |
|
||||
| Веб сайт фронтенд с API на javascript | **Статика:** `index.html` + `style.css` + `script.js` + еще файлы для фронтенда, ванильный JS. БЕЗ сборки, БЕЗ React/Vue/Vite, БЕЗ npm. Tailwind или bootstrap — только CDN. | старт с `design.md`, публикация в `pages` |
|
||||
| Telegram-бот | **Node.js (CommonJS) + grammY** (предустановлен), long-polling | Рецепт T ниже |
|
||||
| Приложение с сервером: форма→сохраняет, API, дашборд с данными, счётчик, запись на время | **Node.js (CommonJS), сервер на `node:http` БЕЗ зависимостей + хранение в `data.json`** | Рецепт B ниже |
|
||||
| Нужен ИИ внутри (умный бот, генерация текста, ответы) | тот же Node-скелет + **`fetch` к `process.env.AI_BASE_URL`** (без SDK) | блок «ИИ» ниже |
|
||||
@ -464,7 +464,7 @@ if (req.method === "POST" && req.url === "/api/upload") {
|
||||
| «приложение для записи» | реальное приложение с сервером: форма → бэкенд хранит записи (файл/JSON) → `run` |
|
||||
| «магазин с корзиной» | рабочий прототип: каталог + корзина на бэкенде; оплату (это внешние договоры) пока замени кнопкой «оформить» → WhatsApp |
|
||||
| «блог» | статика (pages) — если без админки; с админкой — бэкенд через `run` |
|
||||
| «сайт-визитка / промо / портфолио» | статичный лендинг + публикация в `pages` — тут это лучший инструмент, а не компромисс |
|
||||
| «сайт-визитка / промо / портфолио» | динамичный лендинг с красивыми и крутыми анимациями + публикация в `pages` — тут это лучший инструмент, а не компромисс |
|
||||
| «соцсеть» | честно скажи, что за один заход не получится, и предложи первый работающий кусок (профиль + лента на бэкенде) — пусть юзер выберет |
|
||||
|
||||
Правила ведения:
|
||||
@ -766,11 +766,11 @@ async function inbox() {
|
||||
5. Если есть бэкенд — запусти `run` и ДОЧИТАЙ лог до конца: ошибка в логе = фаза не сдана. Если только статика — посмотри вкладку Предпросмотр.
|
||||
6. Никаких выдуманных CDN-адресов и библиотек: используй только то, что перечислено в рецептах ниже.
|
||||
|
||||
## 📁 ТЫ УЖЕ ВНУТРИ ПАПКИ ПРОЕКТА — собирай сайт ЗДЕСЬ
|
||||
## 📁 ТЫ УЖЕ ВНУТРИ ПАПКИ ПРОЕКТА — собирай проект ЗДЕСЬ
|
||||
|
||||
Твоя рабочая директория (cwd) — это **папка проекта юзера** (`.../users/<username>/<project>/`). Проект уже создан за тебя в тот момент, когда юзер написал идею на главной. Проверь: `pwd` — папка проекта, `ls` — там лежат AGENTS.md/design.md/README.md.
|
||||
|
||||
**Собирай сайт ПРЯМО В ТЕКУЩЕЙ папке:** создавай `index.html`, `style.css`, `script.js` здесь же, в cwd.
|
||||
**Собирай сайт ПРЯМО В ТЕКУЩЕЙ папке:** создавай `index.html`, `style.css`, `script.js` и другие необходимые файлы здесь же, в cwd.
|
||||
|
||||
❌ **НЕ запускай `./new-project`.** ❌ **НЕ делай `cd` в другие папки / в корень воркспейса.** Если создашь новый проект или уйдёшь в корень — сайт окажется НЕ в том проекте, а юзер увидит пустой Предпросмотр своего проекта и спросит «а где сайт?». Именно так это ломается.
|
||||
|
||||
@ -808,8 +808,8 @@ git push origin HEAD:pages
|
||||
|
||||
- **Pages 404** → запушь ветку `pages` снова: `git push origin HEAD:pages -f`
|
||||
|
||||
### ⚠️ Проекты с бэкендом (server.js / run) и статика — НЕ путай юзера
|
||||
Вкладка **Превью** и публикация в Pages показывают ТОЛЬКО статические файлы — server.js там НЕ работает: формы method=POST и запросы к твоему серверу будут мертвы. Поэтому:
|
||||
### ⚠️ Проекты с бэкендом (server.js / run) — НЕ путай юзера
|
||||
Вкладка **Превью** и публикация в Pages показывают ТОЛЬКО файлы — server.js там НЕ работает: формы method=POST и запросы к твоему серверу будут мертвы. Поэтому:
|
||||
- Прежде чем переделывать работающий статический index.html на серверный рендер (форма POST, шаблоны из server.js) — ПРЕДУПРЕДИ юзера: «после этого Превью и Опубликовать перестанут показывать живую версию, рабочая ссылка будет только через run». Меняй только после его согласия.
|
||||
- Если проект уже с бэкендом: держи index.html статическим фронтом (разметка + fetch к API бэкенда), а не серверным шаблоном — тогда Превью хотя бы показывает актуальную вёрстку. Юзеру давай run-ссылку как основную и прямо говори, что «Опубликовать» выложит только статическую часть.
|
||||
- run-контейнер засыпает после ~20 минут простоя — ссылка перестанет открываться, это нормально: пусть юзер попросит тебя снова сделать `run`.
|
||||
|
||||
318
API.md
Normal file
318
API.md
Normal file
@ -0,0 +1,318 @@
|
||||
# API Документация — Apple Style App
|
||||
|
||||
## 📚 Обзор
|
||||
|
||||
Бэкенд построен на **Node.js http** (без зависимостей). Данные хранятся в `data.json`.
|
||||
|
||||
---
|
||||
|
||||
## 🔌 PUBLIC API (без авторизации)
|
||||
|
||||
### Получить все продукты
|
||||
```
|
||||
GET /api/products
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Vision Pro",
|
||||
"tagline": "Пространственный компьютер здесь",
|
||||
"description": "...",
|
||||
"price": 3499,
|
||||
"image": "https://...",
|
||||
"features": ["M2", "R1"],
|
||||
"category": "premium",
|
||||
"order": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Получить продукт по ID
|
||||
```
|
||||
GET /api/products/:id
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": { /* объект продукта */ }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Получить активные объявления
|
||||
```
|
||||
GET /api/announcements
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Apple Event 2026",
|
||||
"date": "2026-09-15",
|
||||
"content": "Смотрите презентацию...",
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Получить настройки сайта
|
||||
```
|
||||
GET /api/settings
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"heroTitle": "Добро пожаловать в будущее",
|
||||
"heroSubtitle": "Инновации, которые меняют мир",
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 ADMIN API (для редактирования)
|
||||
|
||||
### Добавить продукт
|
||||
```
|
||||
POST /api/admin/products
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "iPhone 18 Pro",
|
||||
"tagline": "Ещё мощнее",
|
||||
"description": "Описание...",
|
||||
"price": 999,
|
||||
"category": "phones",
|
||||
"image": "https://...",
|
||||
"features": ["A18 чип", "Face ID", "5G"]
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": { /* созданный продукт с id и order */ }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Обновить продукт
|
||||
```
|
||||
PUT /api/admin/products/:id
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Новое название",
|
||||
"price": 1099
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Удалить продукт
|
||||
```
|
||||
DELETE /api/admin/products/:id
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"deleted": 123
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Добавить объявление
|
||||
```
|
||||
POST /api/admin/announcements
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "Специальное предложение",
|
||||
"date": "2026-12-01",
|
||||
"content": "Скидки до 30%",
|
||||
"active": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Обновить объявление
|
||||
```
|
||||
PUT /api/admin/announcements/:id
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "Обновлённый заголовок",
|
||||
"active": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Удалить объявление
|
||||
```
|
||||
DELETE /api/admin/announcements/:id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Обновить настройки сайта
|
||||
```
|
||||
PUT /api/admin/settings
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"heroTitle": "Новый заголовок",
|
||||
"heroSubtitle": "Новый подзаголовок",
|
||||
"theme": "light"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗂 Структура данных (data.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"id": number,
|
||||
"name": string,
|
||||
"tagline": string,
|
||||
"description": string,
|
||||
"price": number,
|
||||
"image": string (URL),
|
||||
"features": string[],
|
||||
"category": string,
|
||||
"order": number
|
||||
}
|
||||
],
|
||||
"announcements": [
|
||||
{
|
||||
"id": number,
|
||||
"title": string,
|
||||
"date": string (ISO date),
|
||||
"content": string,
|
||||
"active": boolean
|
||||
}
|
||||
],
|
||||
"siteSettings": {
|
||||
"heroTitle": string,
|
||||
"heroSubtitle": string,
|
||||
"theme": string ("dark" | "light")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Архитектура сервера
|
||||
|
||||
```
|
||||
server.js
|
||||
├── Routes (API)
|
||||
│ ├── GET /api/products → список продуктов
|
||||
│ ├── GET /api/products/:id → продукт по ID
|
||||
│ ├── GET /api/announcements → активные объявления
|
||||
│ ├── GET /api/settings → настройки сайта
|
||||
│ │
|
||||
│ └── ADMIN (CRUD)
|
||||
│ ├── POST /api/admin/products
|
||||
│ ├── PUT /api/admin/products/:id
|
||||
│ ├── DELETE /api/admin/products/:id
|
||||
│ ├── POST /api/admin/announcements
|
||||
│ ├── PUT /api/admin/announcements/:id
|
||||
│ ├── DELETE /api/admin/announcements/:id
|
||||
│ └── PUT /api/admin/settings
|
||||
│
|
||||
└── Static Files
|
||||
├── / → index.html
|
||||
├── /admin.html
|
||||
├── /style.css
|
||||
├── /app.js
|
||||
└── /admin.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Примеры использования (Frontend)
|
||||
|
||||
### Загрузка продуктов
|
||||
```javascript
|
||||
async function loadProducts() {
|
||||
const res = await fetch('/api/products');
|
||||
const json = await res.json();
|
||||
if (json.ok) {
|
||||
console.log(json.data); // массив продуктов
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Добавление продукта (из админки)
|
||||
```javascript
|
||||
async function addProduct(data) {
|
||||
const res = await fetch('/api/admin/products', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json();
|
||||
return json.ok;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Безопасность (TODO для продакшена)
|
||||
|
||||
Сейчас API открыт. Для продакшена добавить:
|
||||
- JWT или session аутентификацию для `/api/admin/*`
|
||||
- Валидацию входных данных
|
||||
- Rate limiting
|
||||
- CORS только для доверенных доменов
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
```bash
|
||||
# Получить все продукты
|
||||
curl http://localhost:3000/api/products
|
||||
|
||||
# Добавить продукт
|
||||
curl -X POST http://localhost:3000/api/admin/products \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"Test","tagline":"Test","price":99,"category":"test","features":[]}'
|
||||
|
||||
# Обновить настройки
|
||||
curl -X PUT http://localhost:3000/api/admin/settings \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"heroTitle":"Hello"}'
|
||||
```
|
||||
243
admin.css
Normal file
243
admin.css
Normal file
@ -0,0 +1,243 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
|
||||
background: #0a0a0a;
|
||||
color: #f5f5f7;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.admin-nav {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: rgba(29, 29, 31, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.admin-nav-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.admin-logo {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #f5f5f7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
color: #2997ff;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.btn-back:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.admin-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.admin-section {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.admin-section h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.admin-card {
|
||||
background: #1d1d1f;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
|
||||
.admin-card h3 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.admin-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
background: #2d2d30;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.list-item-info {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.list-item-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.list-item-meta {
|
||||
font-size: 14px;
|
||||
color: #86868b;
|
||||
}
|
||||
|
||||
.list-item-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: #86868b;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
background: #0a0a0a;
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
border-radius: 10px;
|
||||
color: #f5f5f7;
|
||||
font-size: 16px;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #2997ff;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border-radius: 980px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #2997ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #147ce5;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: #2997ff;
|
||||
border: 2px solid #2997ff;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #2997ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ff3b30;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #d63027;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 980px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background: rgba(41, 151, 255, 0.15);
|
||||
color: #2997ff;
|
||||
}
|
||||
|
||||
.status-badge.inactive {
|
||||
background: rgba(134, 134, 139, 0.15);
|
||||
color: #86868b;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.list-item-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
138
admin.html
Normal file
138
admin.html
Normal file
@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Админ-панель — Apple Style</title>
|
||||
<link rel="stylesheet" href="admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="admin-nav">
|
||||
<div class="admin-nav-container">
|
||||
<a href="index.html" class="admin-logo"> Админка</a>
|
||||
<a href="index.html" class="btn-back">← На сайт</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="admin-container">
|
||||
<!-- Settings Section -->
|
||||
<section class="admin-section">
|
||||
<h2>🏠 Настройки сайта</h2>
|
||||
<div class="admin-card">
|
||||
<form id="settingsForm">
|
||||
<div class="form-group">
|
||||
<label>Заголовок Hero</label>
|
||||
<input type="text" id="heroTitle" name="heroTitle" placeholder="Добро пожаловать в будущее">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Подзаголовок Hero</label>
|
||||
<input type="text" id="heroSubtitle" name="heroSubtitle" placeholder="Инновации, которые меняют мир">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Тема</label>
|
||||
<select id="theme" name="theme">
|
||||
<option value="dark">Тёмная</option>
|
||||
<option value="light">Светлая</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Сохранить настройки</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Products Section -->
|
||||
<section class="admin-section">
|
||||
<h2>📱 Продукты</h2>
|
||||
<div class="admin-card">
|
||||
<button class="btn btn-secondary" onclick="showAddProductForm()">+ Добавить продукт</button>
|
||||
</div>
|
||||
|
||||
<div id="addProductForm" class="admin-card" style="display:none">
|
||||
<h3>Новый продукт</h3>
|
||||
<form id="productForm">
|
||||
<div class="form-group">
|
||||
<label>Название</label>
|
||||
<input type="text" id="productName" required placeholder="iPhone 18 Pro">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Слоган</label>
|
||||
<input type="text" id="productTagline" required placeholder="Ещё мощнее. Ещё лучше.">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Описание</label>
|
||||
<textarea id="productDescription" rows="3" placeholder="Подробное описание..."></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Цена ($)</label>
|
||||
<input type="number" id="productPrice" required placeholder="999">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Категория</label>
|
||||
<select id="productCategory">
|
||||
<option value="premium">Premium</option>
|
||||
<option value="audio">Audio</option>
|
||||
<option value="wearables">Wearables</option>
|
||||
<option value="computers">Computers</option>
|
||||
<option value="phones">Phones</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>URL изображения</label>
|
||||
<input type="text" id="productImage" placeholder="https://picsum.photos/800/600?random=1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Особенности (через запятую)</label>
|
||||
<input type="text" id="productFeatures" placeholder="M3 чип, 20ч работы, Face ID">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Добавить</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddProductForm()">Отмена</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="productsList" class="admin-list">
|
||||
<!-- Products will be loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Announcements Section -->
|
||||
<section class="admin-section">
|
||||
<h2>📢 Объявления</h2>
|
||||
<div class="admin-card">
|
||||
<button class="btn btn-secondary" onclick="showAddAnnouncementForm()">+ Добавить объявление</button>
|
||||
</div>
|
||||
|
||||
<div id="addAnnouncementForm" class="admin-card" style="display:none">
|
||||
<h3>Новое объявление</h3>
|
||||
<form id="announcementForm">
|
||||
<div class="form-group">
|
||||
<label>Заголовок</label>
|
||||
<input type="text" id="announcementTitle" required placeholder="Apple Event 2026">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Дата</label>
|
||||
<input type="date" id="announcementDate" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Содержание</label>
|
||||
<textarea id="announcementContent" rows="3" required placeholder="Смотрите презентацию..."></textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Добавить</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddAnnouncementForm()">Отмена</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="announcementsList" class="admin-list">
|
||||
<!-- Announcements will be loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
267
admin.js
Normal file
267
admin.js
Normal file
@ -0,0 +1,267 @@
|
||||
// ===================================
|
||||
// ADMIN PANEL - JAVASCRIPT
|
||||
// ===================================
|
||||
|
||||
const API = {
|
||||
products: '/api/products',
|
||||
adminProducts: '/api/admin/products',
|
||||
announcements: '/api/announcements',
|
||||
adminAnnouncements: '/api/admin/announcements',
|
||||
settings: '/api/settings',
|
||||
adminSettings: '/api/admin/settings'
|
||||
};
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadSettings();
|
||||
loadProducts();
|
||||
loadAnnouncements();
|
||||
setupForms();
|
||||
});
|
||||
|
||||
// Setup forms
|
||||
function setupForms() {
|
||||
document.getElementById('settingsForm').addEventListener('submit', saveSettings);
|
||||
document.getElementById('productForm').addEventListener('submit', addProduct);
|
||||
document.getElementById('announcementForm').addEventListener('submit', addAnnouncement);
|
||||
}
|
||||
|
||||
// ============ SETTINGS ============
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const res = await fetch(API.settings);
|
||||
const json = await res.json();
|
||||
if (json.ok && json.data) {
|
||||
document.getElementById('heroTitle').value = json.data.heroTitle || '';
|
||||
document.getElementById('heroSubtitle').value = json.data.heroSubtitle || '';
|
||||
document.getElementById('theme').value = json.data.theme || 'dark';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load settings:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings(e) {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
heroTitle: document.getElementById('heroTitle').value,
|
||||
heroSubtitle: document.getElementById('heroSubtitle').value,
|
||||
theme: document.getElementById('theme').value
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(API.adminSettings, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.ok) {
|
||||
alert('Настройки сохранены! ✅');
|
||||
} else {
|
||||
alert('Ошибка: ' + json.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Ошибка сохранения: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ PRODUCTS ============
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const res = await fetch(API.products);
|
||||
const json = await res.json();
|
||||
const list = document.getElementById('productsList');
|
||||
|
||||
if (!json.ok || !json.data || json.data.length === 0) {
|
||||
list.innerHTML = '<p style="color:#86868b;text-align:center;padding:40px">Нет продуктов</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = json.data.map(p => `
|
||||
<div class="list-item">
|
||||
<div class="list-item-info">
|
||||
<div class="list-item-title">${p.name}</div>
|
||||
<div class="list-item-meta">
|
||||
${p.tagline} • $${p.price} •
|
||||
<span class="status-badge">${p.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-item-actions">
|
||||
<button class="btn btn-secondary btn-small" onclick="editProduct(${p.id})">Изменить</button>
|
||||
<button class="btn btn-danger btn-small" onclick="deleteProduct(${p.id})">Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (e) {
|
||||
console.error('Failed to load products:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddProductForm() {
|
||||
document.getElementById('addProductForm').style.display = 'block';
|
||||
document.getElementById('productForm').reset();
|
||||
}
|
||||
|
||||
function hideAddProductForm() {
|
||||
document.getElementById('addProductForm').style.display = 'none';
|
||||
}
|
||||
|
||||
async function addProduct(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const features = document.getElementById('productFeatures').value
|
||||
.split(',')
|
||||
.map(f => f.trim())
|
||||
.filter(f => f);
|
||||
|
||||
const data = {
|
||||
name: document.getElementById('productName').value,
|
||||
tagline: document.getElementById('productTagline').value,
|
||||
description: document.getElementById('productDescription').value,
|
||||
price: parseFloat(document.getElementById('productPrice').value),
|
||||
category: document.getElementById('productCategory').value,
|
||||
image: document.getElementById('productImage').value || 'https://picsum.photos/800/600?random=' + Date.now(),
|
||||
features: features
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(API.adminProducts, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (json.ok) {
|
||||
alert('Продукт добавлен! ✅');
|
||||
hideAddProductForm();
|
||||
loadProducts();
|
||||
} else {
|
||||
alert('Ошибка: ' + json.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function editProduct(id) {
|
||||
const res = await fetch(`${API.products}/${id}`);
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.ok) return alert('Продукт не найден');
|
||||
|
||||
const p = json.data;
|
||||
if (confirm(`Редактировать "${p.name}"?\n\n(В полной версии откроется форма редактирования)`)) {
|
||||
// В полной версии здесь открывается форма с заполненными данными
|
||||
window.location.href = `edit-product.html?id=${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteProduct(id) {
|
||||
if (!confirm('Вы уверены? Это действие нельзя отменить.')) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API.adminProducts}/${id}`, { method: 'DELETE' });
|
||||
const json = await res.json();
|
||||
|
||||
if (json.ok) {
|
||||
alert('Продукт удалён');
|
||||
loadProducts();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ ANNOUNCEMENTS ============
|
||||
async function loadAnnouncements() {
|
||||
try {
|
||||
const res = await fetch(API.announcements);
|
||||
const json = await res.json();
|
||||
const list = document.getElementById('announcementsList');
|
||||
|
||||
if (!json.ok || !json.data || json.data.length === 0) {
|
||||
list.innerHTML = '<p style="color:#86868b;text-align:center;padding:40px">Нет объявлений</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = json.data.map(a => `
|
||||
<div class="list-item">
|
||||
<div class="list-item-info">
|
||||
<div class="list-item-title">${a.title}</div>
|
||||
<div class="list-item-meta">
|
||||
${a.content} • ${a.date} •
|
||||
<span class="status-badge">${a.active ? 'Активно' : 'Скрыто'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-item-actions">
|
||||
<button class="btn btn-secondary btn-small" onclick="editAnnouncement(${a.id})">Изменить</button>
|
||||
<button class="btn btn-danger btn-small" onclick="deleteAnnouncement(${a.id})">Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (e) {
|
||||
console.error('Failed to load announcements:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddAnnouncementForm() {
|
||||
document.getElementById('addAnnouncementForm').style.display = 'block';
|
||||
document.getElementById('announcementForm').reset();
|
||||
}
|
||||
|
||||
function hideAddAnnouncementForm() {
|
||||
document.getElementById('addAnnouncementForm').style.display = 'none';
|
||||
}
|
||||
|
||||
async function addAnnouncement(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const data = {
|
||||
title: document.getElementById('announcementTitle').value,
|
||||
date: document.getElementById('announcementDate').value,
|
||||
content: document.getElementById('announcementContent').value
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(API.adminAnnouncements, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (json.ok) {
|
||||
alert('Объявление добавлено! ✅');
|
||||
hideAddAnnouncementForm();
|
||||
loadAnnouncements();
|
||||
} else {
|
||||
alert('Ошибка: ' + json.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function editAnnouncement(id) {
|
||||
if (confirm(`Редактировать объявление #${id}?\n\n(В полной версии откроется форма)`)) {
|
||||
window.location.href = `edit-announcement.html?id=${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAnnouncement(id) {
|
||||
if (!confirm('Удалить это объявление?')) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API.adminAnnouncements}/${id}`, { method: 'DELETE' });
|
||||
const json = await res.json();
|
||||
|
||||
if (json.ok) {
|
||||
alert('Объявление удалено');
|
||||
loadAnnouncements();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
}
|
||||
}
|
||||
166
app.js
Normal file
166
app.js
Normal file
@ -0,0 +1,166 @@
|
||||
// ===================================
|
||||
// APPLE-STYLE APP - FRONTEND
|
||||
// ===================================
|
||||
|
||||
const API = {
|
||||
products: '/api/products',
|
||||
announcements: '/api/announcements',
|
||||
settings: '/api/settings'
|
||||
};
|
||||
|
||||
// Load settings and content
|
||||
async function init() {
|
||||
await loadSettings();
|
||||
await loadAnnouncements();
|
||||
await loadProducts();
|
||||
setupScrollEffects();
|
||||
setupRevealAnimations();
|
||||
}
|
||||
|
||||
// Load site settings
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const res = await fetch(API.settings);
|
||||
const json = await res.json();
|
||||
if (json.ok && json.data) {
|
||||
document.getElementById('heroTitle').textContent = json.data.heroTitle || 'Добро пожаловать';
|
||||
document.getElementById('heroSubtitle').textContent = json.data.heroSubtitle || 'Инновации здесь';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load settings:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load announcements
|
||||
async function loadAnnouncements() {
|
||||
try {
|
||||
const res = await fetch(API.announcements);
|
||||
const json = await res.json();
|
||||
const bar = document.getElementById('announcementBar');
|
||||
if (json.ok && json.data.length > 0) {
|
||||
const ann = json.data[0];
|
||||
bar.innerHTML = `${ann.title} — ${ann.content} <a href="#">Подробнее →</a>`;
|
||||
} else {
|
||||
bar.classList.add('hidden');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load announcements:', e);
|
||||
document.getElementById('announcementBar').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Load products
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const res = await fetch(API.products);
|
||||
const json = await res.json();
|
||||
const container = document.getElementById('productsContainer');
|
||||
if (!json.ok || !json.data) {
|
||||
container.innerHTML = '<p style="text-align:center;color:var(--text-secondary)">Нет продуктов</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = json.data.map((p, i) => `
|
||||
<div class="product-card" style="transition-delay: ${i * 100}ms">
|
||||
<img src="${p.image}" alt="${p.name}" class="product-image">
|
||||
<div class="product-content">
|
||||
<h3 class="product-name">${p.name}</h3>
|
||||
<p class="product-tagline">${p.tagline}</p>
|
||||
<p class="product-price">от ${p.price} $</p>
|
||||
<div class="product-features">
|
||||
${p.features.map(f => `<span class="feature-tag">${f}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Trigger visibility check after load
|
||||
setTimeout(checkProductVisibility, 100);
|
||||
} catch (e) {
|
||||
console.error('Failed to load products:', e);
|
||||
document.getElementById('productsContainer').innerHTML = '<p style="text-align:center;color:var(--text-secondary)">Ошибка загрузки</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll effects for navigation
|
||||
function setupScrollEffects() {
|
||||
const nav = document.getElementById('nav');
|
||||
const heroBg = document.getElementById('heroBg');
|
||||
const featuresBg = document.getElementById('featuresBg');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
|
||||
// Nav background
|
||||
if (scrolled > 100) {
|
||||
nav.classList.add('scrolled');
|
||||
} else {
|
||||
nav.classList.remove('scrolled');
|
||||
}
|
||||
|
||||
// Hero parallax
|
||||
if (scrolled < window.innerHeight) {
|
||||
heroBg.style.transform = `translateY(${scrolled * 0.5}px) scale(${1 + scrolled * 0.0008})`;
|
||||
}
|
||||
|
||||
// Features parallax
|
||||
const featuresSection = document.getElementById('features');
|
||||
if (featuresSection) {
|
||||
const rect = featuresSection.getBoundingClientRect();
|
||||
if (rect.top < window.innerHeight && rect.bottom > 0) {
|
||||
const offset = (window.innerHeight - rect.top) * 0.2;
|
||||
featuresBg.style.transform = `translateY(${offset}px)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reveal animations on scroll
|
||||
function setupRevealAnimations() {
|
||||
const reveals = document.querySelectorAll('.reveal');
|
||||
|
||||
const checkReveal = () => {
|
||||
reveals.forEach(el => {
|
||||
const top = el.getBoundingClientRect().top;
|
||||
const trigger = window.innerHeight * 0.85;
|
||||
if (top < trigger) {
|
||||
el.classList.add('active');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', checkReveal);
|
||||
checkReveal();
|
||||
}
|
||||
|
||||
// Check product cards visibility
|
||||
function checkProductVisibility() {
|
||||
const cards = document.querySelectorAll('.product-card');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
cards.forEach(card => observer.observe(card));
|
||||
}
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
const href = this.getAttribute('href');
|
||||
if (href !== '#') {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(href);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
init();
|
||||
});
|
||||
62
data.json
Normal file
62
data.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Vision Pro",
|
||||
"tagline": "Пространственный компьютер здесь",
|
||||
"description": "Революционный интерфейс между человеком и машиной. Погрузитесь в мир цифровых возможностей.",
|
||||
"price": 3499,
|
||||
"image": "https://picsum.photos/800/600?random=1",
|
||||
"features": ["M2 чип", "R1 сопроцессор", "23 млн пикселей", "visionOS"],
|
||||
"category": "premium",
|
||||
"order": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "AirPods Max",
|
||||
"tagline": "Звук в новом измерении",
|
||||
"description": "Высокая точность звука. Активное шумоподавление. Пространственное аудио.",
|
||||
"price": 549,
|
||||
"image": "https://picsum.photos/800/600?random=2",
|
||||
"features": ["ANC", "40ч работы", "Пространственное аудио", "H1 чип"],
|
||||
"category": "audio",
|
||||
"order": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Apple Watch Ultra",
|
||||
"tagline": "Приключения ждут",
|
||||
"description": "Самые прочные и функциональные часы для экстремальных условий.",
|
||||
"price": 799,
|
||||
"image": "https://picsum.photos/800/600?random=3",
|
||||
"features": ["49мм титан", "36ч работы", "GPS", "100м водолазные"],
|
||||
"category": "wearables",
|
||||
"order": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "MacBook Pro 16",
|
||||
"tagline": "Мощность профессионала",
|
||||
"description": "Чип M3 Max. Невероятная производительность для самых требовательных задач.",
|
||||
"price": 2499,
|
||||
"image": "https://picsum.photos/800/600?random=4",
|
||||
"features": ["M3 Max", "128GB RAM", "8TB SSD", "22ч работы"],
|
||||
"category": "computers",
|
||||
"order": 4
|
||||
}
|
||||
],
|
||||
"announcements": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Apple Event 2026",
|
||||
"date": "2026-09-15",
|
||||
"content": "Смотрите презентацию новых продуктов в прямом эфире",
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"siteSettings": {
|
||||
"heroTitle": "Добро пожаловать в будущее",
|
||||
"heroSubtitle": "Инновации, которые меняют мир",
|
||||
"theme": "dark"
|
||||
}
|
||||
}
|
||||
741
index.html
741
index.html
@ -3,668 +3,171 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Исландия — Земля льда и пламени</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: #0B0F19;
|
||||
--text: #FFFFFF;
|
||||
--text-muted: #94A3B8;
|
||||
--accent: #00D4FF;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow-x: hidden;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Навигация */
|
||||
nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(11, 15, 25, 0.9);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -1px;
|
||||
background: linear-gradient(135deg, #00D4FF, #7C3AED);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Hero секция */
|
||||
.hero {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.parallax-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 120%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.hero-bg1 {
|
||||
background-image: url('https://picsum.photos/1920/1200?random=10');
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-bg2 {
|
||||
background-image: url('https://picsum.photos/1920/1200?random=11');
|
||||
z-index: 2;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(11,15,25,0.3) 0%, var(--bg) 100%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
text-align: center;
|
||||
max-width: 900px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(56px, 12vw, 128px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -4px;
|
||||
line-height: 1;
|
||||
margin-bottom: 24px;
|
||||
text-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
animation: fadeUp 1.2s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: clamp(20px, 3vw, 28px);
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 40px;
|
||||
font-weight: 300;
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
animation: fadeUp 1.2s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 18px 42px;
|
||||
border-radius: 980px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: all 0.4s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent), #7C3AED);
|
||||
color: white;
|
||||
box-shadow: 0 10px 40px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-4px) scale(1.02);
|
||||
box-shadow: 0 20px 60px rgba(0, 212, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Destinations */
|
||||
.destinations {
|
||||
padding: 140px 24px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
color: var(--accent);
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(40px, 6vw, 72px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
.destinations-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.destination-card {
|
||||
position: relative;
|
||||
height: 500px;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: transform 0.4s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.destination-card:hover {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
.destination-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.destination-card:hover .destination-img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.destination-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 40px;
|
||||
background: linear-gradient(0deg, rgba(0,0,0,0.9) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
.destination-card h3 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.destination-card p {
|
||||
color: var(--text-muted);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Parallax Section */
|
||||
.parallax-section {
|
||||
position: relative;
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
margin: 80px 0;
|
||||
}
|
||||
|
||||
.parallax-bg {
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 140%;
|
||||
background-image: url('https://picsum.photos/1920/1200?random=12');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.parallax-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(11, 15, 25, 0.7);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.parallax-content {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.parallax-content h2 {
|
||||
font-size: clamp(40px, 7vw, 80px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -2px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.parallax-content p {
|
||||
font-size: 20px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.stats {
|
||||
padding: 120px 24px;
|
||||
background: #080B14;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-item h3 {
|
||||
font-size: clamp(48px, 6vw, 80px);
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--accent), #7C3AED);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stat-item p {
|
||||
color: var(--text-muted);
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* Gallery */
|
||||
.gallery {
|
||||
padding: 140px 24px;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(2, 300px);
|
||||
gap: 16px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gallery-item {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-item:first-child {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.gallery-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.gallery-item:hover .gallery-img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* CTA */
|
||||
.cta {
|
||||
padding: 140px 24px;
|
||||
text-align: center;
|
||||
background: linear-gradient(180deg, var(--bg) 0%, #1a1f2e 100%);
|
||||
}
|
||||
|
||||
.cta h2 {
|
||||
font-size: clamp(40px, 6vw, 72px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -2px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.cta p {
|
||||
font-size: 20px;
|
||||
color: var(--text-muted);
|
||||
max-width: 600px;
|
||||
margin: 0 auto 48px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
padding: 60px 24px;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
margin-bottom: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeUp {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: all 1s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.reveal.active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.nav-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.gallery-item:first-child {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.destinations-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<title>Apple Style — Будущее здесь</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигация -->
|
||||
<nav>
|
||||
<!-- Announcement Bar -->
|
||||
<div id="announcementBar" class="announcement-bar"></div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="nav" id="nav">
|
||||
<div class="nav-container">
|
||||
<div class="logo">ICELAND</div>
|
||||
<a href="/" class="logo"></a>
|
||||
<div class="nav-links">
|
||||
<a href="#destinations">Места</a>
|
||||
<a href="#about">О стране</a>
|
||||
<a href="#gallery">Галерея</a>
|
||||
<a href="#book">Забронировать</a>
|
||||
<a href="#products">Продукты</a>
|
||||
<a href="#features">Возможности</a>
|
||||
<a href="#vision">Vision</a>
|
||||
<a href="admin.html" class="admin-link">Админка</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="hero">
|
||||
<div class="parallax-layer hero-bg1" id="heroBg1"></div>
|
||||
<div class="parallax-layer hero-bg2" id="heroBg2"></div>
|
||||
<!-- Hero Section -->
|
||||
<section class="hero" id="hero">
|
||||
<div class="hero-bg" id="heroBg"></div>
|
||||
<div class="hero-overlay"></div>
|
||||
<div class="hero-content">
|
||||
<h1>Земля льда и пламени</h1>
|
||||
<p>Открой для себя самую загадочную страну мира</p>
|
||||
<a href="#book" class="btn btn-primary">Начать путешествие</a>
|
||||
<h1 id="heroTitle" class="hero-title"></h1>
|
||||
<p id="heroSubtitle" class="hero-subtitle"></p>
|
||||
<div class="hero-cta">
|
||||
<a href="#products" class="btn btn-primary">Смотреть продукты</a>
|
||||
<a href="#vision" class="btn btn-secondary">Узнать больше</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll-indicator">
|
||||
<div class="mouse">
|
||||
<div class="wheel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Destinations -->
|
||||
<section class="destinations" id="destinations">
|
||||
<div class="section-header reveal">
|
||||
<p class="section-label">Направления</p>
|
||||
<h2 class="section-title">Места, которые захватывают дух</h2>
|
||||
<!-- Product Showcase (Apple-style scroll) -->
|
||||
<section class="product-showcase" id="products">
|
||||
<div class="showcase-intro">
|
||||
<h2 class="showcase-title">Выберите свой путь</h2>
|
||||
<p class="showcase-subtitle">Инновации в каждой детали</p>
|
||||
</div>
|
||||
<div class="destinations-grid">
|
||||
<div class="destination-card reveal">
|
||||
<img src="https://picsum.photos/600/800?random=20" alt="Водопад" class="destination-img">
|
||||
<div class="destination-content">
|
||||
<h3>Водопад Гюдльфосс</h3>
|
||||
<p>Один из самых красивых водопадов Исландии</p>
|
||||
<div class="products-container" id="productsContainer">
|
||||
<!-- Products will be loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section with Parallax -->
|
||||
<section class="features" id="features">
|
||||
<div class="features-bg" id="featuresBg"></div>
|
||||
<div class="features-overlay"></div>
|
||||
<div class="features-content">
|
||||
<h2 class="section-title reveal">Технологии будущего</h2>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card reveal">
|
||||
<div class="feature-icon">🚀</div>
|
||||
<h3>Производительность</h3>
|
||||
<p>Чипы M-серии меняют правила игры</p>
|
||||
</div>
|
||||
<div class="destination-card reveal">
|
||||
<img src="https://picsum.photos/600/800?random=21" alt="Гейзер" class="destination-img">
|
||||
<div class="destination-content">
|
||||
<h3>Долина гейзеров</h3>
|
||||
<p>Гейзер Строккур извергается каждые 5-10 минут</p>
|
||||
<div class="feature-card reveal">
|
||||
<div class="feature-icon">🔒</div>
|
||||
<h3>Безопасность</h3>
|
||||
<p>Защита данных на аппаратном уровне</p>
|
||||
</div>
|
||||
<div class="feature-card reveal">
|
||||
<div class="feature-icon">🌱</div>
|
||||
<h3>Экология</h3>
|
||||
<p>Углеродная нейтральность к 2030</p>
|
||||
</div>
|
||||
<div class="destination-card reveal">
|
||||
<img src="https://picsum.photos/600/800?random=22" alt="Лагуна" class="destination-img">
|
||||
<div class="destination-content">
|
||||
<h3>Голубая лагуна</h3>
|
||||
<p>Геотермальный спа-курорт мирового класса</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="destination-card reveal">
|
||||
<img src="https://picsum.photos/600/800?random=23" alt="Северное сияние" class="destination-img">
|
||||
<div class="destination-content">
|
||||
<h3>Северное сияние</h3>
|
||||
<p>Природное явление, которое нужно увидеть хотя бы раз</p>
|
||||
<div class="feature-card reveal">
|
||||
<div class="feature-icon">🤖</div>
|
||||
<h3>ИИ</h3>
|
||||
<p>Apple Intelligence в каждом устройстве</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Parallax Section -->
|
||||
<section class="parallax-section" id="about">
|
||||
<div class="parallax-bg" id="parallaxBg"></div>
|
||||
<div class="parallax-overlay"></div>
|
||||
<div class="parallax-content reveal">
|
||||
<h2>Приключение всей жизни</h2>
|
||||
<p>Вулканы, ледники, водопады, гейзеры и северное сияние — Исландия собрала все чудеса природы в одном месте</p>
|
||||
<a href="#book" class="btn btn-primary">Выбрать тур</a>
|
||||
<!-- Vision Section -->
|
||||
<section class="vision" id="vision">
|
||||
<div class="vision-content">
|
||||
<h2 class="vision-title reveal">Vision Pro</h2>
|
||||
<p class="vision-subtitle reveal">Эра пространственных вычислений наступила</p>
|
||||
<div class="vision-features reveal">
|
||||
<div class="vision-feature">
|
||||
<span class="vision-feature-icon">👁️</span>
|
||||
<div>
|
||||
<h4>eyeSight</h4>
|
||||
<p>Оставайтесь на связи с окружающими</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vision-feature">
|
||||
<span class="vision-feature-icon">🎭</span>
|
||||
<div>
|
||||
<h4>visionOS</h4>
|
||||
<p>Интуитивный пространственный интерфейс</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vision-feature">
|
||||
<span class="vision-feature-icon">🎬</span>
|
||||
<div>
|
||||
<h4>Immersion</h4>
|
||||
<p>Погружение как в кинотеатре</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vision-image reveal">
|
||||
<img src="https://picsum.photos/1200/800?random=50" alt="Vision Pro">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Stats -->
|
||||
<section class="stats">
|
||||
<div class="stats-container">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item reveal">
|
||||
<h3>10K+</h3>
|
||||
<p>Водопадов</p>
|
||||
<!-- Big Image Scroll Section -->
|
||||
<section class="scroll-gallery" id="gallery">
|
||||
<div class="gallery-item" id="galleryItem1">
|
||||
<img src="https://picsum.photos/1920/1080?random=60" alt="">
|
||||
<div class="gallery-caption">
|
||||
<h3>Дизайн</h3>
|
||||
<p>Каждая линия имеет значение</p>
|
||||
</div>
|
||||
<div class="stat-item reveal">
|
||||
<h3>130</h3>
|
||||
<p>Вулканов</p>
|
||||
</div>
|
||||
<div class="stat-item reveal">
|
||||
<h3>600</h3>
|
||||
<p>Горячих источников</p>
|
||||
<div class="gallery-item" id="galleryItem2">
|
||||
<img src="https://picsum.photos/1920/1080?random=61" alt="">
|
||||
<div class="gallery-caption">
|
||||
<h3>Инновации</h3>
|
||||
<p>Технологии, которые вдохновляют</p>
|
||||
</div>
|
||||
<div class="stat-item reveal">
|
||||
<h3>24/7</h3>
|
||||
<p>Северное сияние зимой</p>
|
||||
</div>
|
||||
<div class="gallery-item" id="galleryItem3">
|
||||
<img src="https://picsum.photos/1920/1080?random=62" alt="">
|
||||
<div class="gallery-caption">
|
||||
<h3>Будущее</h3>
|
||||
<p>Создаём завтрашний день сегодня</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Gallery -->
|
||||
<section class="gallery" id="gallery">
|
||||
<div class="section-header reveal">
|
||||
<p class="section-label">Галерея</p>
|
||||
<h2 class="section-title">Моменты, которые останутся навсегда</h2>
|
||||
<!-- CTA Section -->
|
||||
<section class="cta-section">
|
||||
<h2 class="cta-title reveal">Готовы к будущему?</h2>
|
||||
<p class="cta-subtitle reveal">Откройте для себя мир инноваций уже сегодня</p>
|
||||
<div class="cta-buttons reveal">
|
||||
<a href="#products" class="btn btn-primary">Выбрать устройство</a>
|
||||
<a href="#" class="btn btn-secondary">Связаться с нами</a>
|
||||
</div>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item reveal">
|
||||
<img src="https://picsum.photos/1200/800?random=30" alt="Пейзаж" class="gallery-img">
|
||||
</div>
|
||||
<div class="gallery-item reveal">
|
||||
<img src="https://picsum.photos/600/600?random=31" alt="Горы" class="gallery-img">
|
||||
</div>
|
||||
<div class="gallery-item reveal">
|
||||
<img src="https://picsum.photos/600/600?random=32" alt="Океан" class="gallery-img">
|
||||
</div>
|
||||
<div class="gallery-item reveal">
|
||||
<img src="https://picsum.photos/600/600?random=33" alt="Ледник" class="gallery-img">
|
||||
</div>
|
||||
<div class="gallery-item reveal">
|
||||
<img src="https://picsum.photos/600/600?random=34" alt="Пляж" class="gallery-img">
|
||||
</div>
|
||||
<div class="gallery-item reveal">
|
||||
<img src="https://picsum.photos/600/600?random=35" alt="Фьорд" class="gallery-img">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="cta" id="book">
|
||||
<h2 class="reveal">Готовы к приключению?</h2>
|
||||
<p class="reveal">Забронируйте тур в Исландию прямо сейчас и получите скидку 20% на групповые поездки</p>
|
||||
<a href="#" class="btn btn-primary reveal">Забронировать тур</a>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<div class="footer-links">
|
||||
<a href="#">О нас</a>
|
||||
<a href="#">Туры</a>
|
||||
<a href="#">Блог</a>
|
||||
<a href="#">О компании</a>
|
||||
<a href="#">Карьера</a>
|
||||
<a href="#">Инвесторам</a>
|
||||
<a href="#">Новости</a>
|
||||
<a href="#">Конфиденциальность</a>
|
||||
<a href="#">Контакты</a>
|
||||
<a href="#">Политика конфиденциальности</a>
|
||||
</div>
|
||||
<p>© 2026 Iceland Travel. Все права защищены.</p>
|
||||
<p class="copyright">© 2026 Apple Style Demo. Все права защищены.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Многослойный параллакс для Hero
|
||||
const heroBg1 = document.getElementById('heroBg1');
|
||||
const heroBg2 = document.getElementById('heroBg2');
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
if (scrolled < window.innerHeight) {
|
||||
heroBg1.style.transform = `translateY(${scrolled * 0.6}px) scale(${1 + scrolled * 0.0008})`;
|
||||
heroBg2.style.transform = `translateY(${scrolled * 0.3}px) scale(${1 + scrolled * 0.0005})`;
|
||||
}
|
||||
});
|
||||
|
||||
// Параллакс для средней секции
|
||||
const parallaxBg = document.getElementById('parallaxBg');
|
||||
const parallaxSection = document.getElementById('parallaxSection');
|
||||
window.addEventListener('scroll', () => {
|
||||
const rect = document.querySelector('.parallax-section').getBoundingClientRect();
|
||||
if (rect.top < window.innerHeight && rect.bottom > 0) {
|
||||
const offset = (window.innerHeight - rect.top) * 0.25;
|
||||
parallaxBg.style.transform = `translateY(${offset}px)`;
|
||||
}
|
||||
});
|
||||
|
||||
// Анимация появления
|
||||
const reveals = document.querySelectorAll('.reveal');
|
||||
function checkReveal() {
|
||||
reveals.forEach(el => {
|
||||
const top = el.getBoundingClientRect().top;
|
||||
const trigger = window.innerHeight * 0.85;
|
||||
if (top < trigger) {
|
||||
el.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
window.addEventListener('scroll', checkReveal);
|
||||
checkReveal();
|
||||
|
||||
// Плавный скролл
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
8
package.json
Normal file
8
package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "apple-style-app",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
}
|
||||
}
|
||||
185
server.js
Normal file
185
server.js
Normal file
@ -0,0 +1,185 @@
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const DATA = path.join(__dirname, "data.json");
|
||||
|
||||
function readData() {
|
||||
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { products: [], announcements: [], siteSettings: {} }; }
|
||||
}
|
||||
|
||||
function writeData(data) {
|
||||
fs.writeFileSync(DATA, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
function parseBody(req) {
|
||||
return new Promise((resolve) => {
|
||||
let s = "";
|
||||
req.on("data", (c) => (s += c));
|
||||
req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } });
|
||||
});
|
||||
}
|
||||
|
||||
function json(res, data, status = 200) {
|
||||
res.writeHead(status, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
|
||||
res.end(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function notFound(res) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("Not Found");
|
||||
}
|
||||
|
||||
const routes = {
|
||||
// PUBLIC API
|
||||
"GET /api/products": (req, res) => {
|
||||
const data = readData();
|
||||
json(res, { ok: true, data: data.products.sort((a, b) => a.order - b.order) });
|
||||
},
|
||||
|
||||
"GET /api/products/:id": (req, res, params) => {
|
||||
const data = readData();
|
||||
const product = data.products.find(p => p.id === parseInt(params.id));
|
||||
if (!product) return json(res, { ok: false, error: "Product not found" }, 404);
|
||||
json(res, { ok: true, data: product });
|
||||
},
|
||||
|
||||
"GET /api/announcements": (req, res) => {
|
||||
const data = readData();
|
||||
json(res, { ok: true, data: data.announcements.filter(a => a.active) });
|
||||
},
|
||||
|
||||
"GET /api/settings": (req, res) => {
|
||||
const data = readData();
|
||||
json(res, { ok: true, data: data.siteSettings });
|
||||
},
|
||||
|
||||
// ADMIN API
|
||||
"POST /api/admin/products": async (req, res) => {
|
||||
const body = await parseBody(req);
|
||||
const data = readData();
|
||||
const newProduct = {
|
||||
id: Date.now(),
|
||||
...body,
|
||||
order: data.products.length + 1
|
||||
};
|
||||
data.products.push(newProduct);
|
||||
writeData(data);
|
||||
json(res, { ok: true, data: newProduct }, 201);
|
||||
},
|
||||
|
||||
"PUT /api/admin/products/:id": async (req, res, params) => {
|
||||
const body = await parseBody(req);
|
||||
const data = readData();
|
||||
const idx = data.products.findIndex(p => p.id === parseInt(params.id));
|
||||
if (idx === -1) return json(res, { ok: false, error: "Product not found" }, 404);
|
||||
data.products[idx] = { ...data.products[idx], ...body, id: parseInt(params.id) };
|
||||
writeData(data);
|
||||
json(res, { ok: true, data: data.products[idx] });
|
||||
},
|
||||
|
||||
"DELETE /api/admin/products/:id": (req, res, params) => {
|
||||
const data = readData();
|
||||
data.products = data.products.filter(p => p.id !== parseInt(params.id));
|
||||
writeData(data);
|
||||
json(res, { ok: true, deleted: parseInt(params.id) });
|
||||
},
|
||||
|
||||
"POST /api/admin/announcements": async (req, res) => {
|
||||
const body = await parseBody(req);
|
||||
const data = readData();
|
||||
const newAnnouncement = { id: Date.now(), ...body, active: true };
|
||||
data.announcements.push(newAnnouncement);
|
||||
writeData(data);
|
||||
json(res, { ok: true, data: newAnnouncement }, 201);
|
||||
},
|
||||
|
||||
"PUT /api/admin/announcements/:id": async (req, res, params) => {
|
||||
const body = await parseBody(req);
|
||||
const data = readData();
|
||||
const idx = data.announcements.findIndex(a => a.id === parseInt(params.id));
|
||||
if (idx === -1) return json(res, { ok: false, error: "Not found" }, 404);
|
||||
data.announcements[idx] = { ...data.announcements[idx], ...body, id: parseInt(params.id) };
|
||||
writeData(data);
|
||||
json(res, { ok: true, data: data.announcements[idx] });
|
||||
},
|
||||
|
||||
"DELETE /api/admin/announcements/:id": (req, res, params) => {
|
||||
const data = readData();
|
||||
data.announcements = data.announcements.filter(a => a.id !== parseInt(params.id));
|
||||
writeData(data);
|
||||
json(res, { ok: true, deleted: parseInt(params.id) });
|
||||
},
|
||||
|
||||
"PUT /api/admin/settings": async (req, res) => {
|
||||
const body = await parseBody(req);
|
||||
const data = readData();
|
||||
data.siteSettings = { ...data.siteSettings, ...body };
|
||||
writeData(data);
|
||||
json(res, { ok: true, data: data.siteSettings });
|
||||
}
|
||||
};
|
||||
|
||||
const mimeTypes = {
|
||||
".html": "text/html; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".js": "application/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".svg": "image/svg+xml"
|
||||
};
|
||||
|
||||
http.createServer(async (req, res) => {
|
||||
const url = req.url.split("?")[0];
|
||||
const method = req.method;
|
||||
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
if (method === "OPTIONS") {
|
||||
res.writeHead(204);
|
||||
return res.end();
|
||||
}
|
||||
|
||||
// API ROUTING
|
||||
for (const [route, handler] of Object.entries(routes)) {
|
||||
const [m, p] = route.split(" ");
|
||||
if (method !== m) continue;
|
||||
|
||||
const routeParts = p.split("/");
|
||||
const urlParts = url.split("/");
|
||||
|
||||
if (routeParts.length !== urlParts.length) continue;
|
||||
|
||||
const params = {};
|
||||
let match = true;
|
||||
for (let i = 0; i < routeParts.length; i++) {
|
||||
if (routeParts[i].startsWith(":")) {
|
||||
params[routeParts[i].slice(1)] = urlParts[i];
|
||||
} else if (routeParts[i] !== urlParts[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
return handler(req, res, params);
|
||||
}
|
||||
}
|
||||
|
||||
// STATIC FILES
|
||||
let file = url === "/" ? "/index.html" : url;
|
||||
const full = path.join(__dirname, file);
|
||||
|
||||
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||||
const ext = path.extname(full);
|
||||
const type = mimeTypes[ext] || "application/octet-stream";
|
||||
res.writeHead(200, { "Content-Type": type });
|
||||
return res.end(fs.readFileSync(full));
|
||||
}
|
||||
|
||||
notFound(res);
|
||||
}).listen(PORT, () => console.log("Server running on port " + PORT));
|
||||
663
style.css
Normal file
663
style.css
Normal file
@ -0,0 +1,663 @@
|
||||
/* ===================================
|
||||
APPLE-STYLE DESIGN SYSTEM
|
||||
=================================== */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #000000;
|
||||
--bg-secondary: #1d1d1f;
|
||||
--bg-tertiary: #2d2d30;
|
||||
--text-primary: #f5f5f7;
|
||||
--text-secondary: #86868b;
|
||||
--text-tertiary: #6e6e73;
|
||||
--accent: #2997ff;
|
||||
--accent-hover: #147ce5;
|
||||
--gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--gradient-2: linear-gradient(180deg, #1a1a2e 0%, #000000 100%);
|
||||
--nav-height: 48px;
|
||||
--transition-smooth: cubic-bezier(0.25, 1, 0.5, 1);
|
||||
--transition-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
overflow-x: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Announcement Bar */
|
||||
.announcement-bar {
|
||||
background: var(--bg-secondary);
|
||||
padding: 12px 24px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
transition: all 0.4s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.announcement-bar.hidden {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.announcement-bar a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.announcement-bar a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--nav-height);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
z-index: 1000;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
transition: all 0.3s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.nav.scrolled {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 22px;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
transition: color 0.3s;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-links .admin-link {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 120%;
|
||||
background: radial-gradient(ellipse at center, #2a2a3e 0%, #0a0a0f 50%, #000000 100%);
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(ellipse at center, transparent 0%, rgba(0,0,0,0.6) 100%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
max-width: 900px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(56px, 10vw, 112px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1.05;
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #b0b0b5 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
animation: fadeUp 1s var(--transition-smooth) forwards;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: clamp(24px, 4vw, 32px);
|
||||
color: var(--text-secondary);
|
||||
font-weight: 400;
|
||||
margin-bottom: 40px;
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
animation: fadeUp 1s var(--transition-smooth) forwards;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
animation: fadeUp 1s var(--transition-smooth) forwards;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 14px 28px;
|
||||
border-radius: 980px;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s var(--transition-smooth);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--accent);
|
||||
border: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Scroll Indicator */
|
||||
.scroll-indicator {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
animation: fadeIn 1s ease forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
.mouse {
|
||||
width: 26px;
|
||||
height: 40px;
|
||||
border: 2px solid var(--text-secondary);
|
||||
border-radius: 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wheel {
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
background: var(--text-secondary);
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
animation: scroll 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes scroll {
|
||||
0%, 100% { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
50% { opacity: 0.5; transform: translateX(-50%) translateY(12px); }
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Product Showcase */
|
||||
.product-showcase {
|
||||
padding: 140px 24px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.showcase-intro {
|
||||
text-align: center;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.showcase-title {
|
||||
font-size: clamp(40px, 6vw, 64px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.showcase-subtitle {
|
||||
font-size: clamp(20px, 3vw, 28px);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.products-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
transition: all 0.6s var(--transition-smooth);
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
}
|
||||
|
||||
.product-card.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-12px);
|
||||
box-shadow: 0 30px 60px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
object-fit: cover;
|
||||
transition: transform 0.6s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.product-card:hover .product-image {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.product-content {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.product-tagline {
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.product-features {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
padding: 6px 14px;
|
||||
border-radius: 980px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Features Section */
|
||||
.features {
|
||||
position: relative;
|
||||
padding: 140px 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.features-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 120%;
|
||||
background: url('https://picsum.photos/1920/1200?random=100') center/cover;
|
||||
opacity: 0.15;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.features-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, var(--bg-primary) 0%, rgba(0,0,0,0.8) 100%);
|
||||
}
|
||||
|
||||
.features-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: clamp(40px, 6vw, 64px);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-bottom: 80px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: rgba(29, 29, 31, 0.6);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 40px 32px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
transition: all 0.4s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
background: rgba(29, 29, 31, 0.8);
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 22px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Vision Section */
|
||||
.vision {
|
||||
padding: 140px 24px;
|
||||
background: var(--bg-secondary);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 60px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vision-content {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.vision-title {
|
||||
font-size: clamp(40px, 5vw, 64px);
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.vision-subtitle {
|
||||
font-size: clamp(20px, 3vw, 26px);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.vision-features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.vision-feature {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.vision-feature-icon {
|
||||
font-size: 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.vision-feature h4 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vision-feature p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.vision-image {
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vision-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Scroll Gallery */
|
||||
.scroll-gallery {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-item {
|
||||
position: relative;
|
||||
height: 80vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gallery-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.gallery-caption {
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
left: 60px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.gallery-caption h3 {
|
||||
font-size: clamp(32px, 5vw, 56px);
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
text-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.gallery-caption p {
|
||||
font-size: 18px;
|
||||
color: rgba(255,255,255,0.9);
|
||||
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* CTA Section */
|
||||
.cta-section {
|
||||
padding: 140px 24px;
|
||||
text-align: center;
|
||||
background: var(--gradient-2);
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: clamp(40px, 6vw, 72px);
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: clamp(20px, 3vw, 26px);
|
||||
color: var(--text-secondary);
|
||||
max-width: 600px;
|
||||
margin: 0 auto 48px;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
padding: 60px 24px;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.copyright {
|
||||
text-align: center;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Reveal Animation */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: all 1s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.reveal.active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.nav-links {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.vision {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.products-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.gallery-caption {
|
||||
left: 24px;
|
||||
bottom: 40px;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user