refactored a gui fully
This commit is contained in:
parent
4395ac4af2
commit
e25281f695
33
README.md
33
README.md
@ -10,16 +10,24 @@
|
|||||||
|
|
||||||
## 📌 О проекте
|
## 📌 О проекте
|
||||||
|
|
||||||
Лендинг-страница для VPN-сервиса **DmitriyVPN**. Сайт рассказывает о тарифах, преимуществах сервиса и помогает пользователям быстро подключиться через Telegram-бот.
|
Лендинг-страница для VPN-сервиса **DmitriyVPN**, полностью переработанная в соответствии с дизайн-системой **Surfshark** (`DESIGN.md`).
|
||||||
|
Дизайн выполнен в концепции светлого SaaS с контрастными тёмными панелями (ритм «dark → light → dark»), фирменным акцентом Surfshark Teal (`#1ebfbf`), конверсионными кнопками Coral Pulse (`#fa3556`) и промо-баннером Promo Gold (`#ffc200`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠 Технологии
|
## 🛠 Технологии и дизайн-система
|
||||||
|
|
||||||
- **HTML5** — семантическая разметка
|
- **HTML5** — семантическая разметка с чистой иерархией
|
||||||
- **CSS3** — стили, анимации, адаптив
|
- **CSS3** — полная система токенов Surfshark Design System:
|
||||||
- **Vanilla JS** — интерактивность (аккордеон FAQ, бургер-меню, тогл тарифов, scroll reveal)
|
- Шрифтовая система: исключительно **Inter** (веса 400, 600, 700; дисплейный трекинг -1.5px на 60px)
|
||||||
- **Google Fonts** — Inter, Fira Code
|
- Цветовая палитра: Surfshark Teal (`#1ebfbf`), Coral Pulse (`#fa3556`), Promo Gold (`#ffc200`), Charcoal Ink (`#16191c`), Tide Tint (`#e8f7f8`), Ash Gray (`#dadadd`), Deep Abyss (`#1e2327`)
|
||||||
|
- Скругления: 48px для карточек, 12px для стандартных кнопок, 64px для Hero-карточки, 96px для pill-элементов
|
||||||
|
- Плоский современный стиль без теней (elevation через контраст и 1px hairline-границы)
|
||||||
|
- **Vanilla JS** — интерактивность:
|
||||||
|
- Динамический переключатель периодов тарифов (1 мес, 3 мес со скидкой -10%, 12 мес со скидкой -20%)
|
||||||
|
- Интерактивный плавный аккордеон FAQ
|
||||||
|
- Адаптивное бургер-меню для мобильных устройств
|
||||||
|
- Плавная анимация появления контента при скролле (Scroll Reveal)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -27,9 +35,10 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
dmitriyvpn/
|
dmitriyvpn/
|
||||||
├── index.html # Основная HTML-разметка
|
├── DESIGN.md # Спецификация дизайн-системы Surfshark
|
||||||
├── styles.css # Таблица стилей
|
├── index.html # Основная HTML-разметка страницы
|
||||||
├── script.js # Клиентские скрипты
|
├── styles.css # Таблица стилей (токены и компоненты)
|
||||||
|
├── script.js # Клиентские скрипты интерактивности
|
||||||
└── README.md # Документация проекта
|
└── README.md # Документация проекта
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -38,19 +47,17 @@ dmitriyvpn/
|
|||||||
## 🚀 Запуск локально
|
## 🚀 Запуск локально
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Открыть страницу в браузере
|
# Открыть страницу в браузеру
|
||||||
start index.html
|
start index.html
|
||||||
```
|
```
|
||||||
|
|
||||||
Или через расширение **Live Server** в VS Code.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Тарифы
|
## 📋 Тарифы
|
||||||
|
|
||||||
| Тариф | Устройства | Цена |
|
| Тариф | Устройства | Цена |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Бесплатный | 1 | 0 ₽ |
|
| Бесплатный | 1 | 0 ₽ / навсегда |
|
||||||
| Standard | 1 | от 87.38 до 109.22 ₽/мес |
|
| Standard | 1 | от 87.38 до 109.22 ₽/мес |
|
||||||
| Pro | 5 | от 155.53 до 194.41 ₽/мес |
|
| Pro | 5 | от 155.53 до 194.41 ₽/мес |
|
||||||
| Premium | 15 | от 233.30 до 291.62 ₽/мес |
|
| Premium | 15 | от 233.30 до 291.62 ₽/мес |
|
||||||
|
|||||||
8
assets/icons/android-tv.svg
Normal file
8
assets/icons/android-tv.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
|
||||||
|
<!-- TV Body -->
|
||||||
|
<rect x="2" y="5" width="28" height="18" rx="3" stroke="#1ebfbf" stroke-width="2.5"/>
|
||||||
|
<!-- TV Stand -->
|
||||||
|
<path d="M12 28L14.5 23H17.5L20 28" stroke="#1ebfbf" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<!-- Play triangle inside TV -->
|
||||||
|
<polygon points="14,11 20,14 14,17" fill="#1ebfbf"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 443 B |
17
assets/icons/android.svg
Normal file
17
assets/icons/android.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
|
||||||
|
<!-- Antennae -->
|
||||||
|
<line x1="10" y1="5" x2="7" y2="1.5" stroke="#1ebfbf" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="22" y1="5" x2="25" y2="1.5" stroke="#1ebfbf" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<!-- Head -->
|
||||||
|
<path d="M7 13C7 8.03 11.03 4 16 4C20.97 4 25 8.03 25 13H7Z" fill="#1ebfbf"/>
|
||||||
|
<circle cx="12" cy="9" r="1.25" fill="#ffffff"/>
|
||||||
|
<circle cx="20" cy="9" r="1.25" fill="#ffffff"/>
|
||||||
|
<!-- Body -->
|
||||||
|
<path d="M7 15C7 14.45 7.45 14 8 14H24C24.55 14 25 14.45 25 15V24C25 25.1 24.1 26 23 26H9C7.9 26 7 25.1 7 24V15Z" fill="#1ebfbf"/>
|
||||||
|
<!-- Arms -->
|
||||||
|
<rect x="3" y="15" width="2.5" height="8" rx="1.25" fill="#1ebfbf"/>
|
||||||
|
<rect x="26.5" y="15" width="2.5" height="8" rx="1.25" fill="#1ebfbf"/>
|
||||||
|
<!-- Legs -->
|
||||||
|
<rect x="11" y="26" width="2.5" height="5" rx="1.25" fill="#1ebfbf"/>
|
||||||
|
<rect x="18.5" y="26" width="2.5" height="5" rx="1.25" fill="#1ebfbf"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 990 B |
5
assets/icons/ios.svg
Normal file
5
assets/icons/ios.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
|
||||||
|
<rect x="7" y="2" width="18" height="28" rx="4.5" stroke="#1ebfbf" stroke-width="2.5"/>
|
||||||
|
<line x1="13" y1="5.5" x2="19" y2="5.5" stroke="#1ebfbf" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="16" cy="25.5" r="1.5" fill="#1ebfbf"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 347 B |
12
assets/icons/linux.svg
Normal file
12
assets/icons/linux.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
|
||||||
|
<!-- Tux Penguin silhouette -->
|
||||||
|
<path d="M16 2C12.5 2 10.5 4.5 10.5 8C10.5 9.5 10 11.5 8.5 13C6.5 15 5 18 5 21C5 24.5 7.5 27 11 27C12.5 27 13.5 26.5 16 26.5C18.5 26.5 19.5 27 21 27C24.5 27 27 24.5 27 21C27 18 25.5 15 23.5 13C22 11.5 21.5 9.5 21.5 8C21.5 4.5 19.5 2 16 2Z" fill="#1ebfbf"/>
|
||||||
|
<ellipse cx="16" cy="18" rx="6" ry="7.5" fill="#ffffff" opacity="0.3"/>
|
||||||
|
<circle cx="13.5" cy="7.5" r="1.5" fill="#ffffff"/>
|
||||||
|
<circle cx="18.5" cy="7.5" r="1.5" fill="#ffffff"/>
|
||||||
|
<!-- Beak -->
|
||||||
|
<path d="M14 9.5C14 9.5 15.5 11.5 16 11.5C16.5 11.5 18 9.5 18 9.5L14 9.5Z" fill="#ffc200"/>
|
||||||
|
<!-- Feet -->
|
||||||
|
<path d="M7 27C6 27 6 29 8 30C10 31 13 30 13 28.5L7 27Z" fill="#ffc200"/>
|
||||||
|
<path d="M25 27C26 27 26 29 24 30C22 31 19 30 19 28.5L25 27Z" fill="#ffc200"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 861 B |
4
assets/icons/macos.svg
Normal file
4
assets/icons/macos.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
|
||||||
|
<path d="M19.8 4.2C20.9 2.8 21.6 0.9 21.4 0C19.7 0.1 17.6 1.1 16.5 2.5C15.5 3.7 14.6 5.6 14.9 7.4C16.8 7.5 18.7 6.4 19.8 4.2Z" fill="#1ebfbf"/>
|
||||||
|
<path d="M21.5 16.3C21.5 12.3 24.8 10.3 24.9 10.2C23 7.5 20.2 7.1 19.2 7C16.8 6.8 14.4 8.5 13.2 8.5C11.9 8.5 10 7.1 8 7.1C5.4 7.1 3 8.6 1.7 10.9C-1 15.6 1 22.5 3.6 26.3C4.9 28.1 6.3 30.1 8.3 30C10.2 29.9 11 28.7 13.2 28.7C15.4 28.7 16.1 29.9 18.1 30C20.2 30.1 21.5 28.2 22.8 26.4C24.3 24.2 24.9 22.1 25 22C24.9 21.9 21.5 20.6 21.5 16.3Z" fill="#1ebfbf"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 606 B |
6
assets/icons/windows.svg
Normal file
6
assets/icons/windows.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
|
||||||
|
<path d="M3 6.8L13.8 5.3V15H3V6.8Z" fill="#1ebfbf"/>
|
||||||
|
<path d="M15.8 5V3.3L29 1.5V15H15.8V5Z" fill="#1ebfbf"/>
|
||||||
|
<path d="M3 17H13.8V26.7L3 25.2V17Z" fill="#1ebfbf"/>
|
||||||
|
<path d="M15.8 17H29V30.5L15.8 28.7V17Z" fill="#1ebfbf"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
1009
index.html
1009
index.html
File diff suppressed because it is too large
Load Diff
63
script.js
63
script.js
@ -1,7 +1,33 @@
|
|||||||
/* ===== BURGER MENU ===== */
|
/* ==========================================================================
|
||||||
|
DmitriyVPN Client Scripts
|
||||||
|
Interactive UI: Theme Toggle, Burger Menu, Billing Toggle, FAQ, Scroll Reveal
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* ===== THEME TOGGLE (Light standard, Dark toggleable) ===== */
|
||||||
|
function initTheme() {
|
||||||
|
const savedTheme = localStorage.getItem('dmitriyvpn_theme');
|
||||||
|
if (savedTheme === 'dark') {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
const current = document.documentElement.getAttribute('data-theme') || 'light';
|
||||||
|
const next = current === 'dark' ? 'light' : 'dark';
|
||||||
|
document.documentElement.setAttribute('data-theme', next);
|
||||||
|
localStorage.setItem('dmitriyvpn_theme', next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize theme immediately
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
/* ===== BURGER & MOBILE NAVIGATION ===== */
|
||||||
function toggleMobile() {
|
function toggleMobile() {
|
||||||
const burger = document.getElementById('burger');
|
const burger = document.getElementById('burger');
|
||||||
const nav = document.getElementById('mobileNav');
|
const nav = document.getElementById('mobileNav');
|
||||||
|
if (!burger || !nav) return;
|
||||||
const isOpen = burger.classList.toggle('open');
|
const isOpen = burger.classList.toggle('open');
|
||||||
nav.classList.toggle('open', isOpen);
|
nav.classList.toggle('open', isOpen);
|
||||||
document.body.style.overflow = isOpen ? 'hidden' : '';
|
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||||
@ -15,6 +41,11 @@ function closeMobile() {
|
|||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close mobile nav on escape key
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') closeMobile();
|
||||||
|
});
|
||||||
|
|
||||||
/* ===== SCROLL REVEAL ===== */
|
/* ===== SCROLL REVEAL ===== */
|
||||||
const revealObserver = new IntersectionObserver((entries) => {
|
const revealObserver = new IntersectionObserver((entries) => {
|
||||||
entries.forEach(entry => {
|
entries.forEach(entry => {
|
||||||
@ -29,16 +60,18 @@ document.querySelectorAll('.reveal').forEach(el => revealObserver.observe(el));
|
|||||||
|
|
||||||
/* ===== BILLING TOGGLE ===== */
|
/* ===== BILLING TOGGLE ===== */
|
||||||
const prices = {
|
const prices = {
|
||||||
month: { standard: 109.22, pro: 194.41, premium: 291.62 },
|
month: { standard: 109.22, pro: 194.41, premium: 291.62 },
|
||||||
'3month': { standard: 98.30, pro: 174.97, premium: 262.46 },
|
'3month': { standard: 98.30, pro: 174.97, premium: 262.46 },
|
||||||
year: { standard: 87.38, pro: 155.53, premium: 233.30 }
|
year: { standard: 87.38, pro: 155.53, premium: 233.30 }
|
||||||
};
|
};
|
||||||
|
|
||||||
function setBilling(period, btn) {
|
function setBilling(period, btn) {
|
||||||
document.querySelectorAll('.billing-btn').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.billing-opt-btn').forEach(b => b.classList.remove('active'));
|
||||||
btn.classList.add('active');
|
btn.classList.add('active');
|
||||||
|
|
||||||
const p = prices[period];
|
const p = prices[period];
|
||||||
|
if (!p) return;
|
||||||
|
|
||||||
const els = {
|
const els = {
|
||||||
'price-from': p.standard,
|
'price-from': p.standard,
|
||||||
'price-standard': p.standard,
|
'price-standard': p.standard,
|
||||||
@ -53,27 +86,33 @@ function setBilling(period, btn) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
el.textContent = val.toFixed(2);
|
el.textContent = val.toFixed(2);
|
||||||
el.classList.remove('updating');
|
el.classList.remove('updating');
|
||||||
}, 150);
|
}, 120);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== FAQ ACCORDION ===== */
|
/* ===== FAQ ACCORDION ===== */
|
||||||
function toggleFaq(btn) {
|
function toggleFaq(btn) {
|
||||||
const item = btn.parentElement;
|
const item = btn.closest('.faq-item');
|
||||||
const answer = item.querySelector('.faq-a');
|
if (!item) return;
|
||||||
|
const answer = item.querySelector('.faq-answer-collapse');
|
||||||
const isOpen = item.classList.contains('open');
|
const isOpen = item.classList.contains('open');
|
||||||
|
|
||||||
// Close all
|
// Close all other open FAQ items
|
||||||
document.querySelectorAll('.faq-item').forEach(i => {
|
document.querySelectorAll('.faq-item').forEach(i => {
|
||||||
i.classList.remove('open');
|
if (i !== item) {
|
||||||
const a = i.querySelector('.faq-a');
|
i.classList.remove('open');
|
||||||
if (a) a.style.maxHeight = null;
|
const a = i.querySelector('.faq-answer-collapse');
|
||||||
|
if (a) a.style.maxHeight = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open if was closed
|
// Toggle current item
|
||||||
if (!isOpen && answer) {
|
if (!isOpen && answer) {
|
||||||
item.classList.add('open');
|
item.classList.add('open');
|
||||||
answer.style.maxHeight = answer.scrollHeight + 'px';
|
answer.style.maxHeight = answer.scrollHeight + 'px';
|
||||||
|
} else if (answer) {
|
||||||
|
item.classList.remove('open');
|
||||||
|
answer.style.maxHeight = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2065
styles.css
2065
styles.css
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user