From a23a5d7d02e495ab0785497c6fef436b6a587cf2 Mon Sep 17 00:00:00 2001 From: neo Date: Sun, 21 Jun 2026 19:52:27 +0500 Subject: [PATCH] =?UTF-8?q?ROI24=20landing=20page=20=E2=80=94=2014=20?= =?UTF-8?q?=D1=81=D0=B5=D0=BA=D1=86=D0=B8=D0=B9,=20ECharts=20dashboard,=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BB=D1=8C=D0=BA=D1=83=D0=BB=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=80,=20FAQ,=20CTA,=20RU=20compliance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full single-page landing: - Hero + интерактивный ECharts дашборд (3 сегмента) - Numbers Strip с count-up анимацией - 3 боли, 3 решения, таймлайн 5 шагов - Bento-сетка для кого, сравнение ROI24 vs как сейчас - Кейс ФармМаркет, команда, калькулятор потерь - Тариф 100К с гарантией, FAQ 6 вопросов, CTA - Footer heavy (ИНН/ОГРН, 152-ФЗ, cookie, privacy modal) - GSAP + ECharts CDN, Inter + JetBrains Mono - Responsive (1024/768/480px), a11y (skip-link, focus-visible) --- .gitignore | 8 + STATUS.md | 75 +- index.html | 1988 ++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 1602 insertions(+), 469 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5270f7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*_files/ +*_(копия)* +attribution-mockup*.html +dashboard-mockup.html +ibm-analytics-analysis.md +*Analytics* +*European* +*ibm* diff --git a/STATUS.md b/STATUS.md index c2183bc..86c0d5f 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,29 +1,52 @@ -# Attribution Dashboard — STATUS.md (2026-06-20) +# ROI24 Landing — STATUS.md (2026-06-21) ## Текущий статус -- Файл: `attribution-mockup.html` (931 строк) -- Сервер: `http://localhost:8765/attribution-mockup.html` -- Гит: `master` — последний коммит `bed5688` +- Файл: `index.html` (1700+ строк, ~76KB) +- Сервер: `http://localhost:8765/index.html` +- Гит: `master` — в процессе push -## Что реализовано -- 3 бизнес-типа: E-com, Услуги, Производство -- 3 вкладки: CPA, Конверсия, LTV + блок «Доход по каналам» -- 4 ECharts-графика + 2 таблицы -- Данные: spend, revenue, cpa, conv, cpc, retention по каналам -- KPI-карточки: CPA, Продажи, Расход, CPA/чек (в рублях) -- Фин-блок: Общий доход, Расход, Прибыль, ROI -- Бар-чарт: доход vs расход по каналам (метки: SEO, VK, Я.Директ, Ретаргет) -- Кривые удержания: retCurve (исправлен конфликт с retention KPI) +## Реализовано (14 секций) +1. **Header** — sticky, glassmorphism blur, логотип, навигация, телефон, CTA +2. **Hero** — «Вы теряете деньги, не зная почему» + дашборд-мокап (ECharts) +3. **Numbers Strip** — 4 анимированных счётчика (count-up при скролле) +4. **Проблемы** — 3 боли (бенто-карточки) +5. **Решение** — 3 преимущества (⚡ живые данные, 👁 видно потери, 📱 с телефона) +6. **Как работает** — 5 шагов таймлайн (бриф → запуск) +7. **Для кого** — bento-grid (e-com, услуги, производство) +8. **Сравнение** — ROI24 vs «как сейчас» таблица (8 параметров) +9. **Кейс** — «ФармМаркет» 42 точки, экономия 840К/мес +10. **Команда** — 3 человека (аватары, роли, опыт) +11. **Калькулятор потерь** — 3 поля (сотрудники, часы, зарплата) → ₽/год +12. **Тариф** — 100К под ключ, гарантия 30 дней, 6 trust badges +13. **FAQ** — 6 вопросов-ответов (аккордеон) +14. **CTA** — Telegram + телефон + мини-форма +15. **Footer** — 4 колонки, ИНН/ОГРН, 152-ФЗ, cookie banner, privacy modal -## Осталось сделать -- [ ] Адаптив (мобилки) -- [ ] Экспорт в CSV/PDF -- [ ] Автообновление данных (API) +### Технические детали +- Шрифты: Inter + JetBrains Mono (fonts.cdnfonts.com, не Google) +- Графики: ECharts 5.5 (CDN), сегменты e-com/services/production +- Анимации: IntersectionObserver (reveal) + GSAP ScrollTrigger (подключён) +- Зависимости: GSAP 3.12.5 + ECharts 5.5.0 (CDN) +- RU compliance: skip link, focus-visible, cookie banner, privacy modal, ИНН/ОГРН, 152-ФЗ, Telegram fixed button +- Аналитика: JSON-LD (schema.org), Open Graph теги +- Responsive: 3 breakpoints (1024, 768, 480px) -## Архитектура +### Зависимости (CDN) +- GSAP 3.12.5: `https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js` +- ScrollTrigger: `https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js` +- ECharts 5.5.0: `https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js` +- Шрифты: `https://fonts.cdnfonts.com/css/inter` + `jetbrains-mono` + +## Структура дашборда (ECharts) ``` -data.e-com/services/production → { kpi, financial, channels[], ltv, trends[] } -tabs: CPA | Конверсия | LTV | Доход по каналам +DATA = { + ecom: { kpis[4], revenue{labels,data}, channels{labels,data} }, + services: { ... }, + production: { ... } +} +Тоглы: [Интернет-магазин] [Услуги] [Производство] +КPI: Выручка, Маржа, Касса, Дебиторка (e-com) +Графики: line (выручка), bar (каналы) ``` ## Команды @@ -31,14 +54,14 @@ tabs: CPA | Конверсия | LTV | Доход по каналам # Сервер cd /home/nik/projects/wow-design/roi24 && python3 -m http.server 8765 -# Применить патч -cd /home/nik/projects/wow-design/roi24 && patch -p1 < patch.diff - # Коммит + пуш cd /home/nik/projects/wow-design/roi24 && git add -A && git commit -m "msg" && git push origin master ``` ## Важные решения -- `retCurve` вместо `retention` для данных графика (чтобы не перезаписывать KPI) -- `Intl.NumberFormat` для рублей -- Метки каналов: SEO, VK, Я.Директ, Ретаргет (rotate: 45) +- Single HTML file — нет зависимости от сборки +- CSS custom properties — все токены в :root +- ECharts pattern: clear() + setOption() — NEVER innerHTML on chart canvases +- cdnfonts вместо Google Fonts — не блокируется провайдерами +- IntersectionObserver вместо GSAP reveal — работает без GSAP +- Cookie banner через localStorage — запоминает выбор diff --git a/index.html b/index.html index 81191ba..ad33e25 100644 --- a/index.html +++ b/index.html @@ -3,467 +3,1569 @@ - ROI24 — Живые цифры бизнеса + ROI24 — Живые цифры бизнеса за 3 дня + + + + + + + + + +/* ============================================================ + PROBLEMS + ============================================================ */ +.problems-grid { + display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; +} +.problem-card { + background: var(--surface); border: 1px solid var(--border); + border-radius: 16px; padding: 32px; transition: all 0.3s; +} +.problem-card:hover { border-color: var(--border-hover); transform: translateY(-2px); } +.problem-icon { + width: 48px; height: 48px; border-radius: 12px; + background: var(--red-dim); color: var(--red); + display: flex; align-items: center; justify-content: center; + margin-bottom: 20px; font-size: 1.25rem; +} +.problem-card h3 { font-size: 1.125rem; font-weight: 700; margin-bottom: 10px; letter-spacing: -0.01em; } +.problem-card p { font-size: 0.875rem; color: var(--text-sec); line-height: 1.6; } + +/* ============================================================ + SOLUTION + ============================================================ */ +.solution { background: var(--surface); } +.solution-grid { + display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; +} +.solution-card { + background: var(--card); border: 1px solid var(--border); + border-radius: 16px; padding: 32px; transition: all 0.3s; +} +.solution-card:hover { border-color: var(--border-hover); transform: translateY(-2px); } +.sol-icon { + font-size: 2rem; margin-bottom: 16px; +} +.solution-card h3 { font-size: 1.125rem; font-weight: 700; margin-bottom: 10px; } +.solution-card p { font-size: 0.875rem; color: var(--text-sec); line-height: 1.6; } + +/* ============================================================ + HOW IT WORKS (Timeline) + ============================================================ */ +.timeline { position: relative; max-width: 640px; } +.timeline::before { + content: ''; position: absolute; left: 23px; top: 0; bottom: 0; + width: 2px; background: var(--border); +} +.tl-step { + position: relative; padding-left: 64px; padding-bottom: 48px; +} +.tl-step:last-child { padding-bottom: 0; } +.tl-dot { + position: absolute; left: 14px; top: 4px; width: 20px; height: 20px; + border-radius: 50%; background: var(--bg); border: 2px solid var(--accent); + display: flex; align-items: center; justify-content: center; +} +.tl-dot span { + font-size: 0.625rem; font-weight: 700; color: var(--accent); + font-family: var(--mono); +} +.tl-step h3 { font-size: 1.125rem; font-weight: 700; margin-bottom: 4px; } +.tl-time { font-size: 0.75rem; color: var(--accent); font-family: var(--mono); margin-bottom: 8px; } +.tl-step p { font-size: 0.875rem; color: var(--text-sec); line-height: 1.6; } + +/* ============================================================ + FOR WHOM (Bento Grid) + ============================================================ */ +.bento { + display: grid; grid-template-columns: 1.2fr 0.8fr 0.8fr; + grid-template-rows: auto auto; gap: 16px; +} +.bento-card { + background: var(--surface); border: 1px solid var(--border); + border-radius: 16px; padding: 32px; transition: all 0.3s; +} +.bento-card:hover { border-color: var(--border-hover); } +.bento-card.featured { grid-row: span 2; } +.bento-card h3 { font-size: 1.25rem; font-weight: 700; margin-bottom: 8px; } +.bento-metrics { font-size: 0.8125rem; color: var(--text-sec); margin-bottom: 16px; line-height: 1.7; } +.bento-example { + font-size: 0.75rem; color: var(--text-dim); font-style: italic; + border-left: 2px solid var(--border); padding-left: 12px; +} + +/* ============================================================ + COMPARISON TABLE + ============================================================ */ +.comparison { background: var(--surface); } +.comp-table { + width: 100%; border-collapse: collapse; font-size: 0.875rem; +} +.comp-table th { + text-align: left; padding: 16px 20px; font-weight: 600; + border-bottom: 2px solid var(--border); font-size: 0.8125rem; + color: var(--text-sec); text-transform: uppercase; letter-spacing: 0.05em; +} +.comp-table th:last-child { color: var(--accent); } +.comp-table td { + padding: 14px 20px; border-bottom: 1px solid var(--border); + color: var(--text-sec); +} +.comp-table tr:hover td { background: rgba(255,255,255,0.02); } +.comp-yes { color: var(--accent); font-weight: 600; } +.comp-no { color: var(--red); } + +/* ============================================================ + CASE STUDY + ============================================================ */ +.case-card { + background: var(--surface); border: 1px solid var(--border); + border-radius: 16px; padding: 48px; position: relative; + overflow: hidden; +} +.case-card::before { + content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; + background: var(--accent); +} +.case-company { + font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.1em; + color: var(--accent); margin-bottom: 16px; font-weight: 600; +} +.case-card h3 { font-size: 1.75rem; font-weight: 800; letter-spacing: -0.02em; margin-bottom: 32px; } +.case-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; margin-bottom: 32px; } +.case-block h4 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-sec); margin-bottom: 12px; } +.case-block ul { font-size: 0.875rem; color: var(--text-sec); line-height: 2; } +.case-block ul li::before { content: '— '; color: var(--accent); } +.case-quote { + border-left: 2px solid var(--accent); padding-left: 20px; + font-style: italic; color: var(--text-sec); font-size: 1rem; line-height: 1.7; + margin-bottom: 12px; +} +.case-author { font-size: 0.8125rem; color: var(--text-dim); } + +/* ============================================================ + TEAM + ============================================================ */ +.team { background: var(--surface); } +.team-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; } +.team-card { + background: var(--card); border: 1px solid var(--border); + border-radius: 16px; padding: 32px; text-align: center; + transition: all 0.3s; +} +.team-card:hover { border-color: var(--border-hover); transform: translateY(-2px); } +.team-avatar { + width: 80px; height: 80px; border-radius: 50%; + background: linear-gradient(135deg, var(--accent-dim), var(--blue-dim)); + margin: 0 auto 16px; display: flex; align-items: center; justify-content: center; + font-size: 1.75rem; font-weight: 800; color: var(--accent); + border: 2px solid var(--border); +} +.team-card h3 { font-size: 1.0625rem; font-weight: 700; margin-bottom: 4px; } +.team-role { font-size: 0.8125rem; color: var(--accent); margin-bottom: 12px; font-weight: 500; } +.team-exp { font-size: 0.8125rem; color: var(--text-sec); line-height: 1.5; } + +/* ============================================================ + CALCULATOR + ============================================================ */ +.calc-wrap { + background: var(--surface); border: 1px solid var(--border); + border-radius: 16px; padding: 48px; max-width: 600px; +} +.calc-field { margin-bottom: 20px; } +.calc-field label { + display: block; font-size: 0.8125rem; color: var(--text-sec); margin-bottom: 6px; +} +.calc-field input { + width: 100%; padding: 12px 16px; background: var(--card); + border: 1px solid var(--border); border-radius: 10px; + color: var(--text); font-size: 1rem; font-family: var(--mono); + transition: border-color 0.2s; +} +.calc-field input:focus { border-color: var(--accent); outline: none; } +.calc-result { + margin-top: 24px; padding: 24px; border-radius: 12px; + background: var(--accent-dim); border: 1px solid rgba(34,197,94,0.2); + text-align: center; display: none; +} +.calc-result.show { display: block; } +.calc-result .calc-big { + font-size: 2rem; font-weight: 800; color: var(--accent); + font-family: var(--mono); margin-bottom: 8px; +} +.calc-result .calc-detail { font-size: 0.8125rem; color: var(--text-sec); } + +/* ============================================================ + PRICING + ============================================================ */ +.pricing { background: var(--surface); } +.price-card { + background: var(--bg); border: 1px solid var(--border); + border-radius: 16px; padding: 48px; text-align: center; + max-width: 480px; margin: 0 auto; position: relative; +} +.price-card::before { + content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%); + width: 60px; height: 3px; background: var(--accent); border-radius: 0 0 4px 4px; +} +.price-name { font-size: 1rem; color: var(--text-sec); margin-bottom: 8px; } +.price-amount { + font-size: 3rem; font-weight: 800; font-family: var(--mono); + letter-spacing: -0.03em; margin-bottom: 4px; +} +.price-amount span { font-size: 0.4em; color: var(--text-sec); font-weight: 400; } +.price-sub { font-size: 0.875rem; color: var(--text-sec); margin-bottom: 32px; } +.price-features { text-align: left; margin-bottom: 32px; } +.price-features li { + padding: 8px 0; font-size: 0.875rem; color: var(--text-sec); + display: flex; align-items: flex-start; gap: 10px; + border-bottom: 1px solid var(--border); +} +.price-features li::before { content: '✓'; color: var(--accent); font-weight: 700; flex-shrink: 0; margin-top: 1px; } +.price-guarantee { + margin-top: 24px; padding: 20px; border-radius: 10px; + background: var(--card); border: 1px solid var(--border); + font-size: 0.8125rem; color: var(--text-sec); line-height: 1.6; +} +.trust-badges { + display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; + margin-top: 24px; +} +.trust-badge { + font-size: 0.75rem; color: var(--text-dim); + display: flex; align-items: center; gap: 4px; +} +.trust-badge::before { content: '✓'; color: var(--accent); font-size: 0.875rem; } + +/* ============================================================ + FAQ + ============================================================ */ +.faq-list { max-width: 720px; margin: 0 auto; } +.faq-item { + border-bottom: 1px solid var(--border); +} +.faq-q { + display: flex; align-items: center; justify-content: space-between; + padding: 20px 0; cursor: pointer; font-weight: 600; + font-size: 1rem; user-select: none; transition: color 0.2s; +} +.faq-q:hover { color: var(--accent); } +.faq-arrow { + flex-shrink: 0; transition: transform 0.3s; font-size: 0.875rem; color: var(--text-dim); +} +.faq-item.open .faq-arrow { transform: rotate(45deg); } +.faq-a { + max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; +} +.faq-a-inner { + padding: 0 0 20px; font-size: 0.875rem; color: var(--text-sec); line-height: 1.7; +} + +/* ============================================================ + CTA + ============================================================ */ +.cta-section { background: var(--surface); } +.cta-box { + text-align: center; padding: 80px 48px; + border-radius: 20px; background: var(--card); + border: 1px solid var(--border); +} +.cta-box h2 { + font-size: clamp(1.75rem, 4vw, 2.5rem); font-weight: 800; + letter-spacing: -0.03em; margin-bottom: 12px; +} +.cta-box p { color: var(--text-sec); margin-bottom: 32px; font-size: 1.0625rem; } +.cta-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; } +.cta-mini-form { + display: flex; gap: 8px; margin-top: 24px; justify-content: center; +} +.cta-mini-form input { + padding: 12px 16px; background: var(--bg); border: 1px solid var(--border); + border-radius: 100px; color: var(--text); width: 220px; +} +.cta-mini-form input:focus { border-color: var(--accent); outline: none; } + +/* ============================================================ + FOOTER + ============================================================ */ +.footer { + border-top: 1px solid var(--border); padding: 64px 0 32px; +} +.footer-grid { + display: grid; grid-template-columns: 1.2fr 0.8fr 0.8fr 0.8fr; gap: 40px; + margin-bottom: 48px; +} +.footer h4 { + font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.1em; + color: var(--text-sec); margin-bottom: 16px; font-weight: 600; +} +.footer p, .footer a { + font-size: 0.8125rem; color: var(--text-sec); line-height: 1.8; +} +.footer a:hover { color: var(--accent); opacity: 1; } +.footer-bottom { + border-top: 1px solid var(--border); padding-top: 24px; + display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; gap: 16px; +} +.footer-bottom p { font-size: 0.75rem; color: var(--text-dim); } +.footer-links { display: flex; gap: 24px; } +.footer-links a { font-size: 0.75rem; color: var(--text-dim); } +.footer-links a:hover { color: var(--text-sec); opacity: 1; } + +/* Fixed Telegram button */ +.tg-float { + position: fixed; bottom: 24px; right: 24px; z-index: 90; + width: 56px; height: 56px; border-radius: 50%; + background: var(--accent); display: flex; align-items: center; + justify-content: center; box-shadow: 0 4px 20px rgba(34,197,94,0.3); + transition: transform 0.2s; +} +.tg-float:hover { transform: scale(1.05); opacity: 1; } +.tg-float svg { color: #000; } + +/* Cookie banner */ +.cookie-banner { + position: fixed; bottom: 0; left: 0; right: 0; z-index: 200; + background: var(--surface); border-top: 1px solid var(--border); + padding: 16px 24px; display: none; align-items: center; + justify-content: space-between; gap: 16px; flex-wrap: wrap; +} +.cookie-banner.show { display: flex; } +.cookie-banner p { font-size: 0.8125rem; color: var(--text-sec); flex: 1; } +.cookie-banner p a { color: var(--accent); text-decoration: underline; } +.cookie-close { + padding: 8px 20px; border-radius: 100px; background: var(--accent); + color: #000; font-weight: 600; font-size: 0.8125rem; cursor: pointer; + border: none; white-space: nowrap; +} + +/* Privacy modal */ +.modal-overlay { + position: fixed; inset: 0; z-index: 300; background: rgba(0,0,0,0.7); + display: none; align-items: center; justify-content: center; + padding: 24px; +} +.modal-overlay.show { display: flex; } +.modal-box { + background: var(--surface); border: 1px solid var(--border); + border-radius: 16px; padding: 40px; max-width: 600px; width: 100%; + max-height: 80vh; overflow-y: auto; +} +.modal-box h2 { font-size: 1.25rem; font-weight: 700; margin-bottom: 16px; } +.modal-box p { font-size: 0.875rem; color: var(--text-sec); line-height: 1.7; margin-bottom: 12px; } +.modal-close { + margin-top: 16px; padding: 10px 24px; border-radius: 100px; + background: var(--accent); color: #000; font-weight: 600; + border: none; cursor: pointer; +} + +/* ============================================================ + RESPONSIVE + ============================================================ */ +@media (max-width: 1024px) { + .hero-inner { grid-template-columns: 1fr; gap: 40px; } + .hero .dash-wrap { order: -1; } + .problems-grid, .solution-grid, .team-grid { grid-template-columns: repeat(2, 1fr); } + .bento { grid-template-columns: 1fr 1fr; } + .bento-card.featured { grid-row: auto; } + .footer-grid { grid-template-columns: 1fr 1fr; } + .case-grid { grid-template-columns: 1fr; } +} +@media (max-width: 768px) { + section { padding: 80px 0; } + .nav-links { display: none; } + .header-phone { display: none; } + .numbers-grid { grid-template-columns: repeat(2, 1fr); gap: 24px; } + .problems-grid, .solution-grid, .team-grid { grid-template-columns: 1fr; } + .bento { grid-template-columns: 1fr; } + .dash-kpis { grid-template-columns: repeat(2, 1fr); } + .dash-charts { grid-template-columns: 1fr; } + .dash-kpi-value { font-size: 1.125rem; } + .comp-table { font-size: 0.75rem; } + .comp-table th, .comp-table td { padding: 10px 12px; } + .footer-grid { grid-template-columns: 1fr; } + .calc-wrap { padding: 24px; } + .cta-box { padding: 48px 24px; } + .cta-mini-form { flex-direction: column; } + .cta-mini-form input { width: 100%; } +} +@media (max-width: 480px) { + .dash-kpis { grid-template-columns: 1fr; } + .hero-actions { flex-direction: column; } + .hero-actions .btn { width: 100%; } +} + +/* ============================================================ + ANIMATION (GSAP-ready classes) + ============================================================ */ +.reveal { + opacity: 0; transform: translateY(24px); + transition: opacity 0.6s ease, transform 0.6s ease; +} +.reveal.visible { + opacity: 1; transform: translateY(0); +} + -
- -
+ - + + + + + + }); + + /* ========================================================== + COOKIE BANNER + ========================================================== */ + if (!localStorage.getItem('roi24_cookie')) { + setTimeout(function() { + document.getElementById('cookie-banner').classList.add('show'); + }, 1500); + } + document.getElementById('cookie-close').addEventListener('click', function() { + document.getElementById('cookie-banner').classList.remove('show'); + localStorage.setItem('roi24_cookie', '1'); + }); + + /* ========================================================== + REVEAL ON SCROLL (IntersectionObserver) + ========================================================== */ + var reveals = document.querySelectorAll('.reveal'); + if ('IntersectionObserver' in window) { + var observer = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' }); + reveals.forEach(function(el) { observer.observe(el); }); + } else { + reveals.forEach(function(el) { el.classList.add('visible'); }); + } + + /* ========================================================== + COUNTER ANIMATION + ========================================================== */ + var counters = document.querySelectorAll('.counter'); + var counterDone = false; + var numbersSection = document.getElementById('numbers'); + + function animateCounters() { + if (counterDone) return; + counterDone = true; + counters.forEach(function(counter) { + var target = parseFloat(counter.dataset.target); + var isDecimal = counter.dataset.decimal === '1'; + var duration = 1500; + var start = 0; + var startTime = null; + + function step(timestamp) { + if (!startTime) startTime = timestamp; + var progress = Math.min((timestamp - startTime) / duration, 1); + // easeOutExpo + var eased = progress === 1 ? 1 : 1 - Math.pow(2, -10 * progress); + var current = eased * target; + counter.textContent = isDecimal ? current.toFixed(1) : Math.floor(current); + if (progress < 1) requestAnimationFrame(step); + } + requestAnimationFrame(step); + }); + } + + if ('IntersectionObserver' in window) { + var counterObs = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + animateCounters(); + counterObs.unobserve(entry.target); + } + }); + }, { threshold: 0.3 }); + counterObs.observe(numbersSection); + } + + /* ========================================================== + CALCULATOR + ========================================================== */ + var calcPeople = document.getElementById('calc-people'); + var calcHours = document.getElementById('calc-hours'); + var calcSalary = document.getElementById('calc-salary'); + var calcResult = document.getElementById('calc-result'); + var calcResultBig = document.getElementById('calc-result-big'); + var calcResultDetail = document.getElementById('calc-result-detail'); + + function calc() { + var people = parseInt(calcPeople.value) || 0; + var hours = parseInt(calcHours.value) || 0; + var salary = parseInt(calcSalary.value) || 0; + + if (people && hours && salary) { + var hoursPerMonth = hours * 4.33; + var salaryPerHour = salary / 168; + var annualCost = Math.round(hoursPerMonth * salaryPerHour * 12 * people); + var monthlyCost = Math.round(annualCost / 12); + + calcResultBig.textContent = '₽' + annualCost.toLocaleString('ru-RU') + ' / год'; + calcResultDetail.textContent = 'Это примерно ₽' + monthlyCost.toLocaleString('ru-RU') + ' в месяц. ROI24 окупается за 3-8 месяцев.'; + calcResult.classList.add('show'); + } else { + calcResult.classList.remove('show'); + } + } + + calcPeople.addEventListener('input', calc); + calcHours.addEventListener('input', calc); + calcSalary.addEventListener('input', calc); + + /* ========================================================== + DASHBOARD PREVIEW — ECharts + ========================================================== */ + if (typeof echarts !== 'undefined') { + var chartRevenue = null; + var chartChannels = null; + + var SEGMENTS = { + ecom: { + kpis: [ + { label: 'Выручка', value: '4.2М ₽', trend: 'up', trendTxt: '+12%' }, + { label: 'Маржа', value: '23%', trend: 'up', trendTxt: '+3%' }, + { label: 'Касса', value: '1.8М ₽', trend: 'up', trendTxt: '+8%' }, + { label: 'Дебиторка', value: '890K ₽', trend: 'down', trendTxt: '-5%' } + ], + revenue: { + labels: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], + data: [3.1,3.4,3.2,3.6,3.8,3.5,3.9,4.0,3.9,4.2,3.7,4.2] + }, + channels: { + labels: ['SEO','Контекст','Прямой','Email','Маркетплейсы'], + data: [35,28,22,8,7] + } + }, + services: { + kpis: [ + { label: 'Выручка', value: '1.8М ₽', trend: 'up', trendTxt: '+8%' }, + { label: 'Маржа', value: '41%', trend: 'up', trendTxt: '+2%' }, + { label: 'Пайплайн', value: '5.2М ₽', trend: 'up', trendTxt: '+15%' }, + { label: 'Win Rate', value: '32%', trend: 'up', trendTxt: '+4%' } + ], + revenue: { + labels: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], + data: [1.4,1.5,1.3,1.6,1.7,1.6,1.5,1.8,1.9,1.8,1.7,1.8] + }, + channels: { + labels: ['Рефералы','SEO','Контент','Прямой','Реклама'], + data: [40,25,18,12,5] + } + }, + production: { + kpis: [ + { label: 'Выпуск', value: '12K шт', trend: 'up', trendTxt: '+5%' }, + { label: 'OEE', value: '78%', trend: 'up', trendTxt: '+3%' }, + { label: 'Себестоимость', value: '₽2 400', trend: 'down', trendTxt: '-2%' }, + { label: 'Брак', value: '1.2%', trend: 'down', trendTxt: '-0.3%' } + ], + revenue: { + labels: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], + data: [2.8,2.9,3.0,3.1,3.2,3.0,3.3,3.4,3.2,3.5,3.3,3.6] + }, + channels: { + labels: ['Оптовик А','Оптовик Б','Ритейл','Прямые','Экспорт'], + data: [35,25,20,12,8] + } + } + }; + + function renderKpis(seg) { + var container = document.getElementById('dash-kpis'); + var html = ''; + seg.kpis.forEach(function(k) { + var arrow = k.trend === 'up' ? '↑' : '↓'; + var cls = k.trend === 'up' ? 'trend-up' : 'trend-down'; + html += '
' + + '
' + k.label + '
' + + '
' + k.value + '
' + + '
' + + '' + arrow + ' ' + k.trendTxt + '' + + '
'; + }); + container.innerHTML = html; + } + + function buildRevenueChart(data) { + return { + tooltip: { trigger: 'axis', backgroundColor: '#161618', borderColor: '#222', textStyle: { color: '#f5f5f5', fontSize: 11 } }, + grid: { left: 12, right: 12, top: 12, bottom: 4, containLabel: true }, + xAxis: { + type: 'category', data: data.labels, + axisLine: { lineStyle: { color: '#1e1e22' } }, + axisLabel: { color: '#52525b', fontSize: 9 } + }, + yAxis: { + type: 'value', + splitLine: { lineStyle: { color: '#1a1a1e' } }, + axisLabel: { color: '#52525b', fontSize: 9, formatter: v => (v >= 1 ? v.toFixed(0) + 'М' : v.toFixed(0) + 'М') } + }, + series: [{ + type: 'line', data: data.data, smooth: 0.4, symbol: 'none', + lineStyle: { width: 2, color: '#22c55e' }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: 'rgba(34,197,94,0.2)' }, + { offset: 1, color: 'rgba(34,197,94,0.02)' } + ]) + } + }], + animationDuration: 600 + }; + } + + function buildChannelsChart(data) { + return { + tooltip: { trigger: 'axis', backgroundColor: '#161618', borderColor: '#222', textStyle: { color: '#f5f5f5', fontSize: 11 } }, + grid: { left: 12, right: 12, top: 12, bottom: 4, containLabel: true }, + xAxis: { + type: 'value', + splitLine: { lineStyle: { color: '#1a1a1e' } }, + axisLabel: { color: '#52525b', fontSize: 9 } + }, + yAxis: { + type: 'category', data: data.labels, + axisLine: { lineStyle: { color: '#1e1e22' } }, + axisLabel: { color: '#52525b', fontSize: 9 } + }, + series: [{ + type: 'bar', data: data.data, barWidth: 14, + itemStyle: { + borderRadius: [0, 4, 4, 0], + color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ + { offset: 0, color: '#22c55e' }, + { offset: 1, color: 'rgba(34,197,94,0.3)' } + ]) + } + }], + animationDuration: 600 + }; + } + + function setSegment(key) { + var seg = SEGMENTS[key]; + if (!seg) return; + + // Update active button + document.querySelectorAll('.dash-seg-btn').forEach(function(b) { + b.classList.toggle('active', b.dataset.seg === key); + }); + + // Update KPIs + renderKpis(seg); + + // Revenue chart + if (!chartRevenue) chartRevenue = echarts.init(document.getElementById('chart-revenue')); + chartRevenue.clear(); + chartRevenue.setOption(buildRevenueChart(seg.revenue)); + + // Channels chart + if (!chartChannels) chartChannels = echarts.init(document.getElementById('chart-channels')); + chartChannels.clear(); + chartChannels.setOption(buildChannelsChart(seg.channels)); + } + + // Segment button event delegation + document.querySelector('.dash-segments').addEventListener('click', function(e) { + var btn = e.target.closest('.dash-seg-btn'); + if (btn) setSegment(btn.dataset.seg); + }); + + // Initial render + setSegment('ecom'); + + // Resize charts on window resize + window.addEventListener('resize', function() { + if (chartRevenue) chartRevenue.resize(); + if (chartChannels) chartChannels.resize(); + }); + } + + /* ========================================================== + SMOOTH SCROLL for anchor links + ========================================================== */ + document.querySelectorAll('a[href^="#"]').forEach(function(link) { + link.addEventListener('click', function(e) { + var target = document.querySelector(this.getAttribute('href')); + if (target) { + e.preventDefault(); + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }); + }); + +})(); + + + + +