/
Akres
/
VPN
Обзор
Документация
Войти
/
Akres
/
VPN
Код
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
210 строк
27 KB
Akres
Create: index.html
28 июл 2026, 23:30
Верифицирован
28 июл 2026, 23:30
a25bb91
Код
Авторство
О чём код?
<!doctype html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><meta name="color-scheme" content="dark"><title>AkresVPN — Бесплатный VPN</title><meta name="description" content="Бесплатные VPN конфиги VLESS, Hysteria2, Trojan по странам и протоколам"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><style> @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700&family=Rubik:wght@400;500;600;700&display=swap');:root{--bg:#0e0e0e;--surface:#1c1c1c;--surface2:#232323;--surface3:#2 … [Строка слишком длинная. Вы можете скачать файл] </style></head><body><div class="topbar-right"><a href="https://pay.cloudtips.ru/p/a09ce4dd" target="_blank" class="donate-corner-btn" aria-label="Поддержать автора"><svg viewbox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" /></svg>Поддержать</a></div><div class="topbar-fixed"><a href="tg://resolve?domain=akresik" class="topbar-btn"><svg viewbox="0 0 24 24"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" /></svg>Написать автору</a><a href="tg://resolve?domain=AkresMC" class="topbar-btn"><svg viewbox="0 0 24 24"><path d="M9.78 18.65l.28-4.23 7.68-6.92c.34-.31-.07-.46-.52-.19L7.74 13.3 3.64 12c-.88-.25-.89-.86.2-1.3l15.97-6.16c.73-.33 1.43.18 1.15 1.3l-2.72 12.81c-.19.91-.74 1.13-1.5.71L12.6 16.3l-1.99 1.93c-.23.23-.42.42-.83.42z" /></svg>Канал</a></div><div class="page-wrap"><section class="hero"><div class="hero-icon"><svg viewbox="0 0 24 24"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z" /></svg></div><h1 class="hero-title">AkresVPN</h1><p class="hero-sub">Бесплатный VPN — быстро, без регистрации и ограничений</p><button class="btn-all" onclick="copyAllConfigs()"><span class="btn-all-icon"><svg viewbox="0 0 24 24"><path d="M16 1H4C2.9 1 2 1.9 2 3v14h2V3h12V1zm3 4H8C6.9 5 6 5.9 6 7v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" /></svg></span><span class="btn-all-text">Скопировать ссылку</span></button><div id="stats-bar" class="stats-bar"></div></section><div id="loading" class="loading-state"><div class="spinner"></div><span>Загрузка…</span></div><div id="main-content" style="display:none"><div class="sort-header"><span class="sort-line"></span><span class="sort-label">Сортировка</span><span class="sort-line"></span></div><div class="tabs-wrap"><button class="tab-btn active" data-tab="countries" onclick="switchTab('countries')">По странам</button><button class="tab-btn" data-tab="protocols" onclick="switchTab('protocols')">Протоколы</button></div><div id="countries-panel" class="tab-panel active"><div class="country-sort-bar"><button class="country-sort-btn active" data-sort="count" onclick="setCountrySort('count', this)">По количеству</button><button class="country-sort-btn" data-sort="alpha" onclick="setCountrySort('alpha', this)">По алфавиту</button></div><div id="countries-list-inner"></div></div><div id="protocols-panel" class="tab-panel"></div></div></div><div id="toast"></div><script>const ALL_URL = 'https://gitverse.ru/api/repos/Akres/VPN/raw/branch/master/all'; const JSON_URL = 'main.json'; const ICONS = { copy: `<svg viewBox="0 0 24 24"><path d="M16 1H4C2.9 1 2 1.9 2 3v14h2V3h12V1zm3 4H8C6.9 5 6 5.9 6 7v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`, check: `<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`, clock: `<svg viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm.01 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67V7z"/></svg>`, }; let clashMode = false; function toggleClash() { clashMode = !clashMode; document.body.classList.toggle('clash-mode', clashMode); const btn = document.getElementById('clash-toggle'); btn.textContent = clashMode ? '✓ Clash режим включён' : 'У меня Clash клиент'; btn.blur(); } function getUrl(url) { return clashMode ? url + '.yaml' : url; } async function copyAllConfigs() { const btn = document.querySelector('.btn-all'); if (btn.disabled) return; btn.disabled = true; btn.classList.add('copied'); const iconEl = btn.querySelector('.btn-all-icon'); const textEl = btn.querySelector('.btn-all-text'); const origIcon = iconEl.innerHTML; const origText = textEl.textContent; iconEl.innerHTML = ICONS.check; textEl.textContent = 'Скопировано!'; try { await navigator.clipboard.writeText(getUrl(ALL_URL)); showToast(clashMode ? 'Clash-ссылка скопирована!' : 'Ссылка на все конфиги скопирована!'); } catch { showToast('Ошибка копирования'); } setTimeout(() => { iconEl.innerHTML = origIcon; textEl.textContent = origText; btn.classList.remove('copied'); btn.disabled = false; }, 2000); } function plural(n) { const mod10 = n % 10, mod100 = n % 100; if (mod10 === 1 && mod100 !== 11) return 'конфиг'; if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return 'конфига'; return 'конфигов'; } async function loadData() { try { const resp = await fetch(JSON_URL); if (!resp.ok) throw new Error(); const data = await resp.json(); document.getElementById('loading').style.display = 'none'; document.getElementById('main-content').style.display = 'block'; renderStats(data); renderProtocols(data); renderCountries(data); } catch { document.getElementById('loading').innerHTML = `<div style="color:#f87171;font-size:.85rem;text-align:center"> Не удалось загрузить данные.<br> Убедитесь, что <strong>main.json</strong> лежит рядом с index.html. </div>`; } } function renderStats(data) { document.getElementById('stats-bar').innerHTML = ` <button class="clash-toggle" id="clash-toggle" onclick="toggleClash()">У меня Clash клиент</button> <div class="stat-chip"><span class="dot"></span>Конфигов: <strong>${data.total}<strong></div> <div class="update-badge">${ICONS.clock} Последнее обновление: <strong>${data.updated_at}</strong></div> `; } function renderProtocols(data) { const container = document.getElementById('protocols-panel'); let html = ''; for (const [protoName, proto] of Object.entries(data.protocols)) { let subHtml = ''; if (proto.security) { subHtml += `<div><div class="proto-sub-title">Шифрование</div><div class="sub-chips">`; for (const [name, sec] of Object.entries(proto.security)) subHtml += chip(name, sec.count, sec.file); subHtml += `</div></div>`; } if (proto.encryption) { subHtml += `<div><div class="proto-sub-title">Шифрование</div><div class="sub-chips">`; for (const [name, enc] of Object.entries(proto.encryption)) subHtml += chip(name, enc.count, enc.file); subHtml += `</div></div>`; } if (proto.transports) { subHtml += `<div><div class="proto-sub-title">Транспорт</div><div class="sub-chips">`; for (const [name, tr] of Object.entries(proto.transports)) subHtml += chip(name, tr.count, tr.file); subHtml += `</div></div>`; } const hasBody = subHtml.length > 0; html += ` <div class="proto-card fade-up"> <div class="proto-header${hasBody ? '' : ' proto-header--simple'}"> <div class="proto-name">${protoName}</div> <div class="proto-header-right"> <span class="proto-count-badge">${proto.count} ${plural(proto.count)}</span> <button class="proto-copy-all" data-url="${proto.file}" onclick="copyUrl(this)"> ${ICONS.copy} Все ${protoName} </button> </div> </div> ${hasBody ? `<div class="proto-body">${subHtml}</div>` : ''} </div>`; } container.innerHTML = html; } function chip(name, count, file) { return `<button class="sub-chip" data-url="${file}" onclick="copyUrl(this)"> ${ICONS.copy}<span>${name}</span><span class="chip-count">${count}</span> </button>`; } let _countriesData = null; let _countrySort = 'count'; function setCountrySort(sort, btn) { _countrySort = sort; document.querySelectorAll('.country-sort-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); if (_countriesData) renderCountriesInner(_countriesData); } function renderCountries(data) { _countriesData = data; renderCountriesInner(data); } function renderCountriesInner(data) { const container = document.getElementById('countries-list-inner'); let entries = Object.entries(data.countries); if (_countrySort === 'count') { entries.sort((a, b) => b[1].count - a[1].count); } else { entries.sort((a, b) => a[1].name.localeCompare(b[1].name, 'ru')); } let html = '<div class="countries-list">'; if (data.bwl && data.bwl.count > 0) { const bwl = data.bwl; const copyBtn = `<button class="country-copy" data-url="${bwl.file}" onclick="copyUrl(this)">${ICONS.copy} Скопировать</button>`; html += ` <div class="country-row fade-up"> <span class="country-flag">${bwl.flag}</span> <div class="country-info"> <div class="country-name">${bwl.name}</div> <div class="country-count">${bwl.count} ${plural(bwl.count)}</div> </div> ${copyBtn} </div>`; } for (const [, c] of entries) { html += ` <div class="country-row fade-up"> <span class="country-flag">${c.flag}</span> <div class="country-info"> <div class="country-name">${c.name}</div> <div class="country-count">${c.count} ${plural(c.count)}</div> </div> <button class="country-copy" data-url="${c.file}" onclick="copyUrl(this)"> ${ICONS.copy} Скопировать </button> </div>`; } container.innerHTML = html + '</div>'; } async function copyUrl(btn) { const url = getUrl(btn.dataset.url); const orig = btn.innerHTML; btn.disabled = true; // replace first svg with check icon const tmp = document.createElement('div'); tmp.innerHTML = btn.innerHTML; const svgEl = tmp.querySelector('svg'); if (svgEl) { const checkDiv = document.createElement('span'); checkDiv.innerHTML = ICONS.check; svgEl.replaceWith(checkDiv.firstElementChild); } btn.innerHTML = tmp.innerHTML; btn.classList.add('copied'); try { await navigator.clipboard.writeText(url); showToast(clashMode ? 'Clash-ссылка скопирована!' : 'Ссылка скопирована!'); } catch { showToast('Ошибка копирования'); } setTimeout(() => { btn.innerHTML = orig; btn.classList.remove('copied'); btn.disabled = false; }, 1800); } function switchTab(tab) { document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); document.querySelector(`.tab-btn[data-tab="${tab}"]`).classList.add('active'); document.getElementById(tab + '-panel').classList.add('active'); } function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove('show'), 2200); } loadData();</script></body></html>