From 9ae6383d9dd9d761e549ba03da4cf9beddf3d39a Mon Sep 17 00:00:00 2001 From: temp Date: Mon, 17 Aug 2026 11:00:06 +0000 Subject: [PATCH] Full Apple-style app with backend API, admin panel, scroll animations --- .gitignore | 5 + AGENTS.md | 14 +- API.md | 318 ++++++++++++++++++++++ admin.css | 243 +++++++++++++++++ admin.html | 138 ++++++++++ admin.js | 267 ++++++++++++++++++ app.js | 166 +++++++++++ data.json | 62 +++++ index.html | 755 +++++++++------------------------------------------ package.json | 8 + server.js | 185 +++++++++++++ style.css | 663 ++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 2191 insertions(+), 633 deletions(-) create mode 100644 .gitignore create mode 100644 API.md create mode 100644 admin.css create mode 100644 admin.html create mode 100644 admin.js create mode 100644 app.js create mode 100644 data.json create mode 100644 package.json create mode 100644 server.js create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bfa8ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +node_modules/ +.vibe42-run.log +.vibe42-run.pid +*.local diff --git a/AGENTS.md b/AGENTS.md index 331fbab..e0e7bf6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ Workspace юзера `temp`. Это **учебная среда**, где обы 1. **работающим воплощением ЕГО идеи**: сайт — опубликован на `https://pages.git.vibe42.kz/temp//`, бот/приложение — запущены через `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///`). Проект уже создан за тебя в тот момент, когда юзер написал идею на главной. Проверь: `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`. diff --git a/API.md b/API.md new file mode 100644 index 0000000..e7f3931 --- /dev/null +++ b/API.md @@ -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"}' +``` diff --git a/admin.css b/admin.css new file mode 100644 index 0000000..073bf91 --- /dev/null +++ b/admin.css @@ -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; + } +} diff --git a/admin.html b/admin.html new file mode 100644 index 0000000..d008309 --- /dev/null +++ b/admin.html @@ -0,0 +1,138 @@ + + + + + + Админ-панель — Apple Style + + + + + +
+ +
+

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

+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+

📱 Продукты

+
+ +
+ + + +
+ +
+
+ + +
+

📢 Объявления

+
+ +
+ + + +
+ +
+
+
+ + + + diff --git a/admin.js b/admin.js new file mode 100644 index 0000000..60e6a42 --- /dev/null +++ b/admin.js @@ -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 = '

Нет продуктов

'; + return; + } + + list.innerHTML = json.data.map(p => ` +
+
+
${p.name}
+
+ ${p.tagline} • $${p.price} • + ${p.category} +
+
+
+ + +
+
+ `).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 = '

Нет объявлений

'; + return; + } + + list.innerHTML = json.data.map(a => ` +
+
+
${a.title}
+
+ ${a.content} • ${a.date} • + ${a.active ? 'Активно' : 'Скрыто'} +
+
+
+ + +
+
+ `).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); + } +} diff --git a/app.js b/app.js new file mode 100644 index 0000000..5c7170d --- /dev/null +++ b/app.js @@ -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} Подробнее →`; + } 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 = '

Нет продуктов

'; + return; + } + + container.innerHTML = json.data.map((p, i) => ` +
+ ${p.name} +
+

${p.name}

+

${p.tagline}

+

от ${p.price} $

+
+ ${p.features.map(f => `${f}`).join('')} +
+
+
+ `).join(''); + + // Trigger visibility check after load + setTimeout(checkProductVisibility, 100); + } catch (e) { + console.error('Failed to load products:', e); + document.getElementById('productsContainer').innerHTML = '

Ошибка загрузки

'; + } +} + +// 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(); +}); diff --git a/data.json b/data.json new file mode 100644 index 0000000..11c67f4 --- /dev/null +++ b/data.json @@ -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" + } +} diff --git a/index.html b/index.html index 5025d0d..6e5870c 100644 --- a/index.html +++ b/index.html @@ -3,668 +3,171 @@ - Исландия — Земля льда и пламени - + Apple Style — Будущее здесь + - -