Unstable
This commit is contained in:
parent
11a84f4926
commit
4c4d325245
19 changed files with 226 additions and 279 deletions
174
Frontend/Web/JS/control-panel.js
Normal file
174
Frontend/Web/JS/control-panel.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
const API_BASE = "https://spectralvpn.ru:8000";
|
||||
|
||||
const getToken = () => localStorage.getItem("access_token");
|
||||
|
||||
const showNotification = (message, type = "success") => {
|
||||
const color = type === "success" ? "#00ffff" : "#ff5555";
|
||||
const notification = document.createElement("div");
|
||||
notification.style.cssText = `
|
||||
position: fixed; top: 20px; right: 20px; padding: 15px 25px; border-radius: 12px;
|
||||
background: rgba(0,0,0,0.95); border: 1px solid ${color}; color: ${color};
|
||||
z-index: 10000; font-weight: 500; box-shadow: 0 4px 15px rgba(0,0,0,0.5);
|
||||
`;
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => notification.remove(), 4000);
|
||||
};
|
||||
|
||||
async function loadPanel() {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
window.location.href = "register.html";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/config/get_info`, {
|
||||
method: "GET",
|
||||
headers: { "X-API-KEY": token }
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem("access_token");
|
||||
window.location.href = "register.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.ok) throw new Error("Failed to load configs");
|
||||
|
||||
const data = await res.json();
|
||||
document.getElementById("userEmail").textContent = "Аккаунт активен";
|
||||
renderConfigs(data.configs || []);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showNotification("Ошибка загрузки данных", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function renderConfigs(configs) {
|
||||
const container = document.getElementById("urlsList");
|
||||
container.innerHTML = "";
|
||||
|
||||
if (configs.length === 0) {
|
||||
container.innerHTML = `<p style="text-align:center; color:#666; padding:40px 20px;">
|
||||
У вас пока нет конфигураций.<br>Нажмите кнопку ниже, чтобы создать первую.
|
||||
</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
configs.forEach(cfg => {
|
||||
const trafficGB = (cfg.bytes_used / (1024 ** 3)).toFixed(2);
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.className = "url-card";
|
||||
card.innerHTML = `
|
||||
<div>
|
||||
<div class="url-name">${cfg.name}</div>
|
||||
<div style="font-size:13px; color:#888; margin-top:6px;">
|
||||
Использовано: ${trafficGB} ГБ
|
||||
</div>
|
||||
</div>
|
||||
<div class="url-actions">
|
||||
<button class="btn-copy" data-config="${cfg.config}">Скопировать ссылку</button>
|
||||
<button class="btn-delete" data-name="${cfg.name}">Удалить</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
card.querySelector(".btn-copy").addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(cfg.config);
|
||||
showNotification(`Конфиг "${cfg.name}" скопирован в буфер`);
|
||||
} catch (e) {
|
||||
showNotification("Не удалось скопировать", "error");
|
||||
}
|
||||
});
|
||||
|
||||
card.querySelector(".btn-delete").addEventListener("click", async () => {
|
||||
if (!confirm(`Удалить конфигурацию "${cfg.name}"?`)) return;
|
||||
|
||||
const token = getToken();
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/config/delete`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"X-API-KEY": token,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ name: cfg.name })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
card.remove();
|
||||
showNotification(`Конфиг "${cfg.name}" удалён`, "error");
|
||||
} else {
|
||||
showNotification("Не удалось удалить конфиг", "error");
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification("Ошибка сети", "error");
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("addConfigBtn").addEventListener("click", async () => {
|
||||
const name = prompt("Введите название конфигурации (например: Телефон, Ноутбук, Рабочий):");
|
||||
if (!name || !name.trim()) return;
|
||||
|
||||
const token = getToken();
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/config/add`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-KEY": token,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ name: name.trim() })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
showNotification(`Конфиг "${name}" успешно создан!`);
|
||||
loadPanel();
|
||||
} else {
|
||||
showNotification(data.detail || "Ошибка при создании конфига", "error");
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification("Нет связи с сервером", "error");
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("deleteAccountBtn").addEventListener("click", async () => {
|
||||
if (!confirm("Вы уверены, что хотите удалить аккаунт? Это действие необратимо!")) return;
|
||||
|
||||
const token = getToken();
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/user/delete`, {
|
||||
method: "DELETE",
|
||||
headers: { "X-API-KEY": token }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
localStorage.removeItem("access_token");
|
||||
alert("Аккаунт успешно удалён.");
|
||||
window.location.href = "index.html";
|
||||
} else {
|
||||
showNotification("Не удалось удалить аккаунт", "error");
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification("Ошибка сети", "error");
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("logoutBtn").addEventListener("click", () => {
|
||||
if (confirm("Выйти из аккаунта?")) {
|
||||
localStorage.removeItem("access_token");
|
||||
window.location.href = "index.html";
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", loadPanel);
|
||||
83
Frontend/Web/JS/register.js
Normal file
83
Frontend/Web/JS/register.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const form = document.getElementById("registerForm");
|
||||
const submitButton = form.querySelector("button[type='submit']");
|
||||
|
||||
const setError = (fieldId, message) => {
|
||||
const errorEl = document.getElementById(fieldId);
|
||||
if (errorEl) {
|
||||
errorEl.textContent = message;
|
||||
errorEl.classList.add("active");
|
||||
}
|
||||
const input = document.getElementById(fieldId.replace("Error", ""));
|
||||
if (input) input.classList.add("invalid");
|
||||
};
|
||||
|
||||
const clearErrors = () => {
|
||||
document.querySelectorAll(".error").forEach(el => {
|
||||
el.textContent = "";
|
||||
el.classList.remove("active");
|
||||
});
|
||||
document.querySelectorAll(".input").forEach(el => el.classList.remove("invalid"));
|
||||
};
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
clearErrors();
|
||||
|
||||
const email = document.getElementById("email").value.trim();
|
||||
const password = document.getElementById("password").value;
|
||||
const passwordReply = document.getElementById("password_reply").value;
|
||||
const promo_code = document.getElementById("promo_code") ? document.getElementById("promo_code").value.trim() : "";
|
||||
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
setError("emailError", "Введите корректный email");
|
||||
return;
|
||||
}
|
||||
if (!password || password.length < 6) {
|
||||
setError("passwordError", "Пароль минимум 6 символов");
|
||||
return;
|
||||
}
|
||||
if (password !== passwordReply) {
|
||||
setError("passwordReplyError", "Пароли не совпадают");
|
||||
return;
|
||||
}
|
||||
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = "Создаём аккаунт...";
|
||||
|
||||
try {
|
||||
const response = await fetch("https://spectralvpn.ru:8000/user/signup", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
password: password,
|
||||
promo_code: promo_code
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// Сохраняем токен
|
||||
localStorage.setItem("access_token", result.access_token);
|
||||
|
||||
window.location.href = "control-panel.html";
|
||||
} else {
|
||||
if (result.detail?.includes("Promo-code")) {
|
||||
setError("promoError", "Неверный промокод");
|
||||
} else if (result.detail?.includes("Email") || result.detail?.includes("busy")) {
|
||||
setError("emailError", "Этот email уже используется");
|
||||
} else {
|
||||
setError("emailError", result.detail || "Ошибка регистрации");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("emailError", "Нет связи с сервером");
|
||||
} finally {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = "Зарегистрироваться";
|
||||
}
|
||||
});
|
||||
});
|
||||
301
Frontend/Web/Styles/control-panel.css
Normal file
301
Frontend/Web/Styles/control-panel.css
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
:root{
|
||||
--bg: #000;
|
||||
--card: #111;
|
||||
--text: #e0e0e0;
|
||||
--accent: cyan;
|
||||
--border: #333;
|
||||
}
|
||||
|
||||
*{margin: 0; padding: 0; box-sizing: border-box;}
|
||||
body{background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; min-height: 100vh;}
|
||||
|
||||
.container{max-width: 800px; margin: 0 auto; padding: 20px;}
|
||||
|
||||
header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #333;
|
||||
margin-bottom: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
header h2 {font-size: 24pt;}
|
||||
header h2 b {color: var(--accent);}
|
||||
|
||||
.user-info{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
#logoutBtn{
|
||||
background: transparent;
|
||||
border: 1px solid var(--accent);
|
||||
color: var(--accent);
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
#logoutBtn:hover{
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h1{
|
||||
font-size: 28pt;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.urls-list{
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.url-card{
|
||||
background: var(--card);
|
||||
border: 1px solid #333;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.url-card:hover{
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 15px rgba(0, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.url-name{
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.url-actions{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-copy, .btn-delete{
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.btn-copy{
|
||||
background: transparent;
|
||||
border: 1px solid var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.btn-copy:hover{
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.btn-delete{
|
||||
background: #330000;
|
||||
border: 1px solid #800;
|
||||
color: #f88;
|
||||
}
|
||||
|
||||
.btn-delete:hover{
|
||||
background: #800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.add-btn{
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
border: 2px dashed var(--accent);
|
||||
color: var(--accent);
|
||||
font-size: 16pt;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition:hover{
|
||||
background: rgba(0, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.modal{
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.8);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal.active{display: flex;}
|
||||
|
||||
.modal-content{
|
||||
background: #111;
|
||||
padding: 30px;
|
||||
border-radius: 16px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
border: 1px solid var(--accent);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-content input{
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
margin: 10px 0;
|
||||
background: #222;
|
||||
border: 1px solid #444;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-content .error{
|
||||
color: #ff6b6b;
|
||||
margin: 10px 0;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.buttons{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.buttons button{
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#loginSubmit{
|
||||
background: var(--accent);
|
||||
color: black;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#closeModal{
|
||||
background: transparent;
|
||||
border: 1px solid #666;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.how-to-connect{
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.platform-tabs{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-btn{
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
color: #aaa;
|
||||
padding: 12px 24px;
|
||||
border-radius: 50px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.22s ease;
|
||||
}
|
||||
|
||||
.tab-btn:hover{
|
||||
border-color: #666;
|
||||
color: #ddd;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.tab-btn.active{
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 20px rgba(0, 255, 255, 0.25);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.instructions{
|
||||
background: var(--card);
|
||||
border: 1px solid #333;
|
||||
border-radius: 16px;
|
||||
padding: 28px 32px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.instructions ol{
|
||||
counter-reset: step;
|
||||
list-style: none;
|
||||
font-size: 15.5px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.instructions li{
|
||||
position: relative;
|
||||
padding-left: 38px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.instructions li::before{
|
||||
content: counter(step);
|
||||
counter-increment: step;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: #222;
|
||||
border: 1px solid var(--accent);
|
||||
color: var(--accent);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.instructions a{
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid rgba(0,255,255,0.3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.instructions a:hover{
|
||||
border-bottom-color: var(--accent);
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
.instructions kbd{
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
padding: 3px 7px;
|
||||
font-family: monospace;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.instructions ul{
|
||||
margin: 12px 0 12px 24px;
|
||||
list-style: disc;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display: none !important;
|
||||
}
|
||||
402
Frontend/Web/Styles/index.css
Normal file
402
Frontend/Web/Styles/index.css
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: 'Inter';
|
||||
}
|
||||
|
||||
header{
|
||||
height: 5vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
border-bottom: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(41, 41, 41);
|
||||
}
|
||||
|
||||
header h2{
|
||||
margin-left: 2vw;
|
||||
}
|
||||
|
||||
h2 b{
|
||||
color: aqua;
|
||||
}
|
||||
|
||||
header div button{
|
||||
height: 4vh;
|
||||
font-size: 14pt;
|
||||
border-width: 0;
|
||||
font-weight: 600;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#register{
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-color: aqua;
|
||||
border-width: 1px;
|
||||
margin-right: 10px;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
#register:hover{
|
||||
color: black;
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
#register:active{
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#login{
|
||||
background-color: aqua;
|
||||
margin-right: 2vw;
|
||||
}
|
||||
|
||||
#login:hover{
|
||||
background-color: black;
|
||||
border-color: aqua;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#login:active{
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#main{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
#main h1{
|
||||
font-size: 25pt;
|
||||
text-align: center;
|
||||
margin-top: 10vh;
|
||||
line-height: 35pt;
|
||||
}
|
||||
|
||||
#main h4{
|
||||
margin-top: 1vh;
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
max-width: 550px;
|
||||
text-align: center;
|
||||
color: lightgrey;
|
||||
line-height: 20pt;
|
||||
}
|
||||
|
||||
#main h4 b{
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#container{
|
||||
display: flex;
|
||||
margin-top: 5vh;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#container div{
|
||||
height: 4vh;
|
||||
width: 100px;
|
||||
background-color: rgba(10, 117, 136, 0.575);
|
||||
border-radius: 7px;
|
||||
border-width: 1px;
|
||||
border-color: aqua;
|
||||
border-style: solid;
|
||||
margin: 5px;
|
||||
color: aqua;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 13pt;
|
||||
font-weight: 700;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#main button{
|
||||
margin-top: 3vh;
|
||||
height: 5vh;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
background-color: aqua;
|
||||
border-radius: 7px;
|
||||
border-style: none;
|
||||
font-size: 14pt;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: 0.1s;
|
||||
margin-bottom: 5vh;
|
||||
}
|
||||
|
||||
#main button:hover{
|
||||
background-color: black;
|
||||
border-color: aqua;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#main button:active{
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#info{
|
||||
background-color: rgb(7, 7, 7);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
padding: 5vh;
|
||||
min-height: 30vh;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
border-top: 1px;
|
||||
border-bottom: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(27, 27, 27);
|
||||
}
|
||||
|
||||
#info div{
|
||||
margin: 5vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#info div h1{
|
||||
font-weight: 300;
|
||||
font-size: 25pt;
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
#info div h3{
|
||||
font-size: 14pt;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
#info div p{
|
||||
width: 250px;
|
||||
text-align: center;
|
||||
color: gray;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
#final{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 5vh;
|
||||
}
|
||||
|
||||
#final h2{
|
||||
font-size: 25pt;
|
||||
}
|
||||
|
||||
#final h3{
|
||||
font-weight: 300;
|
||||
color:gray;
|
||||
margin-top: 2vh;
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
line-height: 17pt;
|
||||
}
|
||||
|
||||
#final button{
|
||||
color: white;
|
||||
background-color: black;
|
||||
border-color: aqua;
|
||||
border-style: solid;
|
||||
border-radius: 7px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
height: 5vh;
|
||||
font-size: 15pt;
|
||||
font-weight: 700;
|
||||
margin-top: 3vh;
|
||||
cursor: pointer;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
#final button:hover{
|
||||
color: black;
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
#final button:active{
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
footer{
|
||||
height: 5vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
border: 0;
|
||||
border-top: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(41, 41, 41);
|
||||
}
|
||||
|
||||
footer a{
|
||||
color: darkgray;
|
||||
}
|
||||
|
||||
footer a:active{
|
||||
color: rgb(109, 109, 109);
|
||||
}
|
||||
|
||||
footer p{
|
||||
color: darkgray;
|
||||
}
|
||||
|
||||
@media (width < 600px) {
|
||||
header {
|
||||
height: 10vh;
|
||||
padding: 0 15px;
|
||||
flex-wrap: wrap;
|
||||
width: 91vw;
|
||||
}
|
||||
|
||||
header h2 {
|
||||
margin-left: 0;
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
header div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
header div button {
|
||||
font-size: 12pt;
|
||||
height: 4.5vh;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#register {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#login {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Main Section */
|
||||
#main h1 {
|
||||
font-size: 20pt;
|
||||
line-height: 28pt;
|
||||
margin-top: 8vh;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
#main h4 {
|
||||
font-size: 12pt;
|
||||
line-height: 18pt;
|
||||
padding: 0 20px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 4vh;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
#container div {
|
||||
width: 45%;
|
||||
min-width: 90px;
|
||||
height: 5.5vh;
|
||||
font-size: 11pt;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main button {
|
||||
margin-top: 4vh;
|
||||
height: 6vh;
|
||||
font-size: 13pt;
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* Info Section */
|
||||
#info {
|
||||
flex-direction: column;
|
||||
padding: 4vh 0;
|
||||
gap: 3vh;
|
||||
}
|
||||
|
||||
#info div {
|
||||
margin: 0;
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#info div h1 {
|
||||
font-size: 22pt;
|
||||
}
|
||||
|
||||
#info div h3 {
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
#info div p {
|
||||
width: 100%;
|
||||
font-size: 12pt;
|
||||
line-height: 16pt;
|
||||
}
|
||||
|
||||
/* Final Section */
|
||||
#final {
|
||||
padding: 6vh 20px;
|
||||
}
|
||||
|
||||
#final h2 {
|
||||
font-size: 20pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#final h3 {
|
||||
width: 100%;
|
||||
font-size: 12pt;
|
||||
line-height: 18pt;
|
||||
margin-top: 2vh;
|
||||
}
|
||||
|
||||
#final button {
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
height: 6vh;
|
||||
font-size: 13pt;
|
||||
margin-top: 4vh;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
padding: 15px 0;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
footer a, footer p {
|
||||
font-size: 11pt;
|
||||
}
|
||||
}
|
||||
108
Frontend/Web/Styles/offer.css
Normal file
108
Frontend/Web/Styles/offer.css
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
:root{
|
||||
--bg: #0f0f1a;
|
||||
--text: #e0e0ff;
|
||||
--accent: cyan;
|
||||
--card: #1a1a2e;
|
||||
--border: #333355;
|
||||
}
|
||||
|
||||
*{
|
||||
box-sizing: border-box; margin:0; padding:0;
|
||||
}
|
||||
|
||||
body{
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.container{
|
||||
max-width: 900px;
|
||||
margin: 40px auto;
|
||||
padding: 30px;
|
||||
background: var(--card);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
header{
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
h1{
|
||||
font-size: 2.2rem; color: var(--accent); margin:0;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-size: 1.5rem; color: var(--accent); margin-top: 2rem; border-left: 4px solid var(--accent); padding-left: 12px;
|
||||
}
|
||||
|
||||
h3{
|
||||
font-size: 1.2rem; color: #c0c0ff; margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
p, li{
|
||||
font-size: 1rem; margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
ul, ol{
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
code{
|
||||
background: #2a2a3a;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.highlight{
|
||||
background: rgba(110, 86, 207, 0.15);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--accent);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.footer{
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: #888;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn{
|
||||
display: inline-block;
|
||||
margin: 15px 10px 0 0;
|
||||
padding: 10px 20px;
|
||||
background: var(--accent);
|
||||
color: var(--card);
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover{
|
||||
background: #5a45b0; transform: translateY(-2px);
|
||||
}
|
||||
|
||||
a{
|
||||
color: var(--accent); text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container { margin: 20px; padding: 20px; }
|
||||
h1 { font-size: 1.8rem; }
|
||||
}
|
||||
156
Frontend/Web/Styles/register.css
Normal file
156
Frontend/Web/Styles/register.css
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
:root{
|
||||
--accent: cyan;
|
||||
--bg-dark: rgb(8, 8, 8);
|
||||
--card-bg: rgb(22, 22, 22);
|
||||
--text: #e0e0e0;
|
||||
--border: var(--accent);
|
||||
}
|
||||
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: var(--bg-dark);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 15px;
|
||||
padding: 30px 25px;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
h1{
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
label{
|
||||
margin-top: 16px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input{
|
||||
padding: 12px 14px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(0, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.input:focus{
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.input::placeholder{
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.checkbox-container{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.checkbox-container input[type="checkbox"]{
|
||||
accent-color: var(--accent);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkbox-container a{
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button{
|
||||
margin-top: 24px;
|
||||
padding: 14px;
|
||||
background-color: black;
|
||||
color: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
background-color: var(--accent);
|
||||
color: black;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
button:active{
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 6px rgba(0, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
p{
|
||||
text-align: center;
|
||||
margin-top: 2vh;
|
||||
}
|
||||
|
||||
.error{
|
||||
color: #ff6b6b;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 6px;
|
||||
min-height: 20px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.error.active{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input.invalid,
|
||||
.checkbox.invalid{
|
||||
border-color: #ff6b6b !important;
|
||||
box-shadow: 0 0 8px rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
button:disabled{
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 480px){
|
||||
form{
|
||||
padding: 25px 20px;
|
||||
}
|
||||
|
||||
h1{
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
88
Frontend/Web/control-panel.html
Normal file
88
Frontend/Web/control-panel.html
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Личный кабинет — SpectralVPN</title>
|
||||
<link rel="stylesheet" href="Styles/control-panel.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h2><b>Spectral</b>VPN</h2>
|
||||
<div class="user-info" id="userInfo">
|
||||
<span id="userEmail"></span>
|
||||
<button id="logoutBtn">Выйти</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="mainContent" class="hidden">
|
||||
<div class="how-to-connect">
|
||||
<h1>Как подключиться</h1>
|
||||
|
||||
<div class="platform-tabs">
|
||||
<button class="tab-btn active" data-platform="android">Android</button>
|
||||
<button class="tab-btn" data-platform="windows">ПК (Windows)</button>
|
||||
<button class="tab-btn" data-platform="ios">iOS</button>
|
||||
</div>
|
||||
|
||||
<div class="instructions" id="android-instructions">
|
||||
<ol>
|
||||
<li>Создайте и скопируйте свою конфигурацию в личном кабинете.</li>
|
||||
<li>Скачайте и установите <a href="https://github.com/2dust/v2rayNG/releases/download/2.0.13/v2rayNG_2.0.13_universal.apk" target="_blank" rel="noopener">приложение v2rayNG</a>.</li>
|
||||
<li>Откройте приложение.</li>
|
||||
<li>Нажмите <b>+</b> в правом верхнем углу → <b>Импорт из буфера обмена</b>.</li>
|
||||
<li><b>Готово!</b> Включайте/выключайте соединение большой кнопкой внизу справа.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="instructions hidden" id="windows-instructions">
|
||||
<ol>
|
||||
<li>Создайте и скопируйте свою конфигурацию в личном кабинете.</li>
|
||||
<li>Скачайте и установите <a href="https://github.com/qr243vbi/nekobox/releases/download/5.10.25/nekobox-5.10.25-windows64-installer.exe" target="_blank" rel="noopener">NekoBox for Windows</a>.</li>
|
||||
<li>Откройте программу.</li>
|
||||
<li>Нажмите <kbd>Ctrl</kbd> + <kbd>V</kbd> — конфигурация добавится автоматически.</li>
|
||||
<li>Выделите добавленную конфигурацию и нажмите <kbd>Enter</kbd>.</li>
|
||||
<li>Вверху включите:
|
||||
<ul>
|
||||
<li><b>Системный прокси</b> — для работы в браузерах</li>
|
||||
<li><b>Режим TUN</b> — для всей системы</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>Готово!</b> Вы подключены.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="instructions hidden" id="ios-instructions">
|
||||
<ol>
|
||||
<li>Создайте и скопируйте свою конфигурацию в личном кабинете.</li>
|
||||
<li>Скачайте приложение <a href="https://apps.apple.com/ru/app/v2raytun/id6476628951" target="_blank" rel="noopener">V2RayTun</a> из App Store.</li>
|
||||
<li>Откройте приложение.</li>
|
||||
<li>Нажмите <b>+</b> в правом верхнем углу → <b>Импорт из буфера обмена</b>.</li>
|
||||
<li><b>Готово!</b> Включайте/выключайте соединение центральной кнопкой.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Ваши конфигурации</h1>
|
||||
<div class="urls-list" id="urlsList"></div>
|
||||
<button class="add-btn" id="addUrlBtn">+ Добавить конфиг</button>
|
||||
</main>
|
||||
|
||||
<div class="modal" id="loginModal">
|
||||
<div class="modal-content">
|
||||
<h2>Вход в аккаунт</h2>
|
||||
<input type="email" id="loginEmail" placeholder="Email" required>
|
||||
<input type="password" id="loginPassword" placeholder="Пароль" required>
|
||||
<div class="error" id="loginError"></div>
|
||||
<div class="buttons">
|
||||
<button id="loginSubmit">Войти</button>
|
||||
<button id="closeModal">Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="JS/control-panel.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
57
Frontend/Web/index.html
Normal file
57
Frontend/Web/index.html
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spectralvpn</title>
|
||||
<link rel="stylesheet" href="Styles/index.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2><b>Spectral</b>VPN</h2>
|
||||
<div>
|
||||
<button id="register" onclick="location.href='register.html'">Зарегистрироваться</button>
|
||||
<button id="login" onclick="location.href='control-panel.html'">Войти</button>
|
||||
</div>
|
||||
</header>
|
||||
<section id="main">
|
||||
<h1>Максимальная скорость<br>и неуязвимое шифрование</h1>
|
||||
<h4>Мы предлагаем современный протокол <b>VLESS + Reality</b> — шифрование трафика без потери скорости.</h4>
|
||||
<div id="container">
|
||||
<div>VLESS</div>
|
||||
<div>Reality</div>
|
||||
<div>TLS 1.3</div>
|
||||
<div>1 Гбит/с</div>
|
||||
</div>
|
||||
<button onclick="location.href='register.html'">Попробовать бесплатно</button>
|
||||
</section>
|
||||
<section id="info">
|
||||
<div>
|
||||
<h1>Speed</h1>
|
||||
<h3>Высокая скорость</h3>
|
||||
<p>До 1 Гбит/с</p>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Lock</h1>
|
||||
<h3>Полное шифрование</h3>
|
||||
<p>VLESS + TLS 1.3 + Reality</p>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Shield</h1>
|
||||
<h3>Reality-маскировка</h3>
|
||||
<p>Трафик выглядит как обычное открытие Web-сайта</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="final">
|
||||
<h2>Подключитесь за 5 минут</h2>
|
||||
<h3>Скачайте конфигурацию VLESS + Reality и импортируйте в любой клиент</h3>
|
||||
<button onclick="location.href='register.html'">Получить бесплатно</button>
|
||||
</section>
|
||||
<footer>
|
||||
<a href="offer.html">Пользовательское соглашение</a>
|
||||
<p>2025 SpectralVPN</p>
|
||||
<a href="privacy.html">Политика конфиденциальности</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
118
Frontend/Web/offer.html
Normal file
118
Frontend/Web/offer.html
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Пользовательское соглашение (Публичная оферта) — SpectralVPN</title>
|
||||
<link rel="stylesheet" href="Styles/offer.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Пользовательское соглашение (Публичная оферта)</h1>
|
||||
<p>VPN-сервис <strong>SpectralVPN</strong></p>
|
||||
<p>Дата вступления в силу: <strong>15 ноября 2025 г.</strong></p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>1. Термины и определения</h2>
|
||||
<p><strong>1.1.</strong> <strong>VPN-сервис SpectralVPN</strong> (далее — «Продавец») — онлайн-сервис, расположенный на доменном имени <a href="https://spectralvpn.ru">spectralvpn.ru</a>, а также его официальная система в мессенджере Telegram с учетной записью <code>@spectralvpnbot</code> (далее — «Телеграм-бот»). Продавец предоставляет платные услуги доступа к виртуальной частной сети (VPN) на условиях настоящего соглашения.</p>
|
||||
<p><strong>1.2.</strong> <strong>Пользователь</strong> (далее — «Покупатель») — дееспособное физическое либо юридическое лицо, посетившее Сайт или воспользовавшееся Телеграм-ботом и намеревающееся приобрести либо использующее услуги VPN-сервиса SpectralVPN. Факт использования Сайта или Телеграм-бота, включая оформление Заказа, означает <strong>полное и безоговорочное принятие</strong> Пользователем условий настоящего соглашения.</p>
|
||||
<p><strong>1.3.</strong> <strong>Услуга</strong> — услуга платного предоставления доступа к VPN-соединению, реализуемая путем выдачи Покупателю <strong>уникального URL для подключения к VPN-серверу по протоколу VLESS с использованием Reality</strong>. URL позволяет установить защищённое соединение с интернетом через серверы Продавца.</p>
|
||||
<p><strong>1.4.</strong> <strong>Заказ</strong> — должным образом оформленный запрос Покупателя на приобретение Услуги через Сайт или Телеграм-бот, совмещённый с внесением оплаты согласно выбранному тарифу. Оплата Заказа Покупателем признаётся <strong>акцептом настоящей оферты</strong>, то есть полным и безусловным принятием условий соглашения.</p>
|
||||
<p><strong>1.5.</strong> <strong>Стороны</strong> — совместное именование Продавца и Покупателя в рамках настоящего соглашения.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>2. Предмет соглашения</h2>
|
||||
<p><strong>2.1.</strong> Продавец обязуется предоставить Покупателю Услугу VPN-доступа на условиях настоящего соглашения, а Покупатель обязуется оплатить предоставление данной Услуги. Услуга заключается в выдаче Покупателю <strong>уникального URL для подключения к VPN-серверу</strong> по протоколу <strong>VLESS+Reality</strong> с целью защиты интернет-соединения и данных Покупателя. Покупатель самостоятельно выбирает клиентское приложение и настраивает подключение.</p>
|
||||
<p><strong>2.2.</strong> Настоящее соглашение является договором возмездного оказания услуг и публичной офертой в соответствии с п.2 ст. 437 ГК РФ. Полным и безоговорочным акцептом оферты признается совершение Покупателем оплаты Заказа. С момента акцепта договор считается заключённым.</p>
|
||||
<p><strong>2.3.</strong> Сервис предоставляется «как есть» (“as is”). Продавец не гарантирует бесперебойную работу на всех устройствах. Характеристики (скорость, задержка, доступность ресурсов) зависят от внешних факторов.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. Порядок оформления заказа и предоставления Услуги</h2>
|
||||
<p><strong>3.1.</strong> Оформление Заказа — через <a href="https://spectralvpn.ru">spectralvpn.ru</a> или <code>@spectralvpnbot</code>. Покупатель предоставляет достоверный email.</p>
|
||||
<p><strong>3.2.</strong> Оплата — 100 % предоплата через ЮKassa. Момент зачисления — начало исполнения.</p>
|
||||
<p><strong>3.3.</strong> URL выдаётся <strong>в течение 72 часов</strong> (обычно мгновенно) в личном кабинете, по email или в Telegram. Обязательства Продавца выполнены с момента передачи работоспособного URL.</p>
|
||||
<p><strong>3.4.</strong> Покупатель самостоятельно настраивает клиент (Nekobox, v2rayNG и др.). Инструкции — на Сайте.</p>
|
||||
<p><strong>3.5.</strong> Продавец вправе отказать в Услуге до оплаты без объяснений. При отказе — полный возврат.</p>
|
||||
<p><strong>3.6.</strong> Возможны плановые работы. Перерывы не являются нарушением.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4. Условия использования и ограничения</h2>
|
||||
<p><strong>4.1.</strong> URL — <strong>один Покупатель</strong>, неограниченное количество устройств. Передача третьим лицам запрещена.</p>
|
||||
<p><strong>4.2.</strong> Запрещено:</p>
|
||||
<ul>
|
||||
<li>Противоправные действия (хакерство, спам, мошенничество)</li>
|
||||
<li>Распространение запрещённых материалов</li>
|
||||
<li><strong>Торренты (P2P) — автоматически блокируются системой 3x-ui</strong></li>
|
||||
<li>Утечка URL в открытый доступ</li>
|
||||
</ul>
|
||||
<p class="highlight"><strong>При нарушении — блокировка без возврата средств.</strong></p>
|
||||
<p><strong>4.3.</strong> Покупатель несёт полную ответственность за свои действия в сети.</p>
|
||||
<p><strong>4.4.</strong> Продавец не ограничивает доступ к ресурсам, но оставляет право блокировки при выявлении нарушений.</p>
|
||||
<p><strong>4.5.</strong> URL действует до конца оплаченного периода. Продление — опционально.</p>
|
||||
<p><strong>4.6.</strong> <strong>IP-адрес сервера может меняться ежемесячно.</strong> Актуальный URL — в личном кабинете.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Стоимость и расчёты</h2>
|
||||
<p><strong>5.1.</strong> Тарифы — в рублях РФ, на Сайте и в боте.</p>
|
||||
<p><strong>5.2.</strong> Цены могут меняться. Изменения не касаются оплаченных Заказов.</p>
|
||||
<p><strong>5.3.</strong> Оплата — 100 % предоплата через ЮKassa. Чек отправляется автоматически (ФЗ-54).</p>
|
||||
<p><strong>5.4.</strong> Услуга считается оказанной с момента выдачи URL.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Возврат средств</h2>
|
||||
<p><strong>6.1.</strong> Возврат возможен <strong>в течение 7 календарных дней</strong> с момента оплаты, если <strong>подключение по URL технически невозможно</strong> по вине Продавца.</p>
|
||||
<p><strong>6.2.</strong> Если URL выдан и работает — <strong>возврат не производится</strong>.</p>
|
||||
<p><strong>6.3.</strong> При невыдаче URL в течение 72 часов — полный возврат в течение 10 рабочих дней.</p>
|
||||
<p><strong>6.4.</strong> При блокировке за нарушение — возврат не производится.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>7. Конфиденциальность и защита данных</h2>
|
||||
<p><strong>7.1.</strong> <strong>No-Logs политика:</strong> Продавец <strong>не ведёт журналы интернет-активности</strong> (IP, трафик, DNS, сайты). Вся передаваемая информация шифруется и не сохраняется.</p>
|
||||
<p><strong>7.2.</strong> Хранятся только:</p>
|
||||
<ul>
|
||||
<li>Email (для входа)</li>
|
||||
<li>Хэш пароля</li>
|
||||
<li>Приватный ключ (UUID)</li>
|
||||
<li>Дата оплаты, тариф</li>
|
||||
<li>Дата принятия соглашения</li>
|
||||
</ul>
|
||||
<p class="highlight"><strong>Объём трафика, IP, DNS — НЕ хранятся.</strong></p>
|
||||
<p><strong>7.3.</strong> Покупатель отвечает за сохранность URL и пароля.</p>
|
||||
<p><strong>7.5.</strong> Обработка данных — в соответствии с <a href="/privacy.html">Политикой конфиденциальности</a>.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>8. Ответственность сторон</h2>
|
||||
<p><strong>8.1.</strong> Ответственность — по законодательству РФ с учётом ограничений.</p>
|
||||
<p><strong>8.2.</strong> Продавец отвечает только за выдачу работоспособного URL.</p>
|
||||
<p><strong>8.6.</strong> Максимальная ответственность — <strong>стоимость последнего оплаченного периода</strong>.</p>
|
||||
<p><strong>8.7.</strong> Форс-мажор: блокировки, сбои связи, действия властей.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>9. Заключительные положения</h2>
|
||||
<p><strong>9.1.</strong> Оферта действует бессрочно. Изменения публикуются на Сайте. Продолжение использования = согласие.</p>
|
||||
<p><strong>9.3.</strong> Недействительность одного пункта не влияет на остальные.</p>
|
||||
<p><strong>9.4.</strong> Споры — в суде по месту нахождения Продавца (РФ).</p>
|
||||
<p><strong>9.5.</strong> <strong>Безусловный акцепт — использование <a href="https://spectralvpn.ru">spectralvpn.ru</a> или <code>@spectralvpnbot</code>.</strong> Начало использования = согласие с офертой.</p>
|
||||
</section>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2025 SpectralVPN. Все права защищены.</p>
|
||||
<p>Самозанятый | ИНН: <em>[вставьте ваш ИНН]</em></p>
|
||||
<p>
|
||||
<a href="index.html" class="btn">На главную</a>
|
||||
<a href="privacy.html" class="btn">Политика конфиденциальности</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
84
Frontend/Web/privacy.html
Normal file
84
Frontend/Web/privacy.html
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Политика конфиденциальности — SpectralVPN</title>
|
||||
<link rel="stylesheet" href="Styles/offer.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Политика конфиденциальности</h1>
|
||||
<p>SpectralVPN — <strong>анонимный VPN без логов активности</strong></p>
|
||||
<p>Дата вступления в силу: 15 ноября 2025 г.</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>1. Общие положения</h2>
|
||||
<p>Настоящая Политика конфиденциальности регулирует порядок обработки персональных данных Пользователей сервиса <strong>SpectralVPN</strong>, расположенного по адресу <a href="https://spectralvpn.ru">spectralvpn.ru</a> и в Telegram-боте <code>@spectralvpnbot</code>.</p>
|
||||
<p>Использование Сервиса означает безоговорочное согласие Пользователя с настоящей Политикой.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>2. Какие данные мы собираем</h2>
|
||||
<p>Мы собираем <strong>минимальный объём данных</strong>, необходимый только для предоставления Услуги:</p>
|
||||
<ul>
|
||||
<li><strong>Email</strong> — для входа и получения URL</li>
|
||||
<li><strong>Хэш пароля</strong> — оригинал не хранится</li>
|
||||
<li><strong>Приватный ключ (UUID)</strong> — для аутентификации по VLESS+Reality</li>
|
||||
<li><strong>Дата оплаты и тариф</strong></li>
|
||||
<li><strong>Дата принятия соглашения</strong></li>
|
||||
</ul>
|
||||
<div class="highlight">
|
||||
<strong>Мы НЕ собираем и НЕ храним:</strong><br>
|
||||
• IP-адреса<br>• Объём трафика<br>• DNS-запросы<br>• Посещаемые сайты<br>• Содержимое передаваемых данных
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. No-Logs политика</h2>
|
||||
<p>Сервис <strong>SpectralVPN построен на принципе нулевого логирования активности</strong>. Весь трафик шифруется сквозным шифрованием и <strong>не сохраняется</strong> на серверах.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4. Как используются данные</h2>
|
||||
<ul>
|
||||
<li>Выдача и активация URL</li>
|
||||
<li>Продление подписки</li>
|
||||
<li>Техподдержка</li>
|
||||
<li>Соблюдение ФЗ-152, ФЗ-54</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Хранение и защита</h2>
|
||||
<p>Данные хранятся на защищённых серверах в Европе (геолокация меняется ежемесячно). Применяется AES-256, ограничение доступа, резервное копирование. Данные удаляются через 30 дней после окончания подписки.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Передача третьим лицам</h2>
|
||||
<p>Передача только по судебному решению РФ или ЮKassa (сумма, email, статус).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>7. Права Пользователя</h2>
|
||||
<p>Запрос доступа, удаления, отзыва согласия (прекратит доступ). Обращайтесь: <a href="mailto:support@spectralvpn.ru">support@spectralvpn.ru</a></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>8. Изменения</h2>
|
||||
<p>Обновления публикуются здесь. Продолжение использования = согласие.</p>
|
||||
</section>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2025 SpectralVPN. Все права защищены.</p>
|
||||
<p>Самозанятый | ИНН: <em>[вставьте ваш ИНН]</em></p>
|
||||
<p>
|
||||
<a href="index.html" class="btn">На главную</a>
|
||||
<a href="offer.html" class="btn">Пользовательское соглашение</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
36
Frontend/Web/register.html
Normal file
36
Frontend/Web/register.html
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Регистрация - SpectralVPN</title>
|
||||
<link rel="stylesheet" href="Styles/register.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="JS/register.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form id="registerForm" novalidate>
|
||||
<h1>Регистрация</h1>
|
||||
<label for="email">Электронная почта</label>
|
||||
<input type="email" id="email" class="input" placeholder="example@domain.com" required autocomplete="email">
|
||||
<div class="error" id="emailError"></div>
|
||||
<label for="password">Пароль</label>
|
||||
<input type="password" id="password" class="input" placeholder="Введите пароль">
|
||||
<div class="error" id="passwordError"></div>
|
||||
<label for="password_reply">Повторите пароль</label>
|
||||
<input type="password" id="password_reply" class="input" placeholder="Повторите пароль">
|
||||
<div class="error" id="passwordReplyError"></div>
|
||||
<label for="promo_code">Промокод</label>
|
||||
<input type="text" id="promo_code" class="input" placeholder="Введите прокод">
|
||||
<div class="error" id="promoError"></div>
|
||||
<div class="checkbox-container">
|
||||
<input type="checkbox" id="terms" class="checkbox" required>
|
||||
<label for="terms">Я прочитал и согласен с <a href="offer.html" target="_blank">пользовательским соглашением</a> и <a href="privacy.html" target="_blank">политикой конфиденциальности</a>.</label>
|
||||
</div>
|
||||
<div class="error" id="termsError"></div>
|
||||
<button type="submit">Зарегистрироваться</button>
|
||||
<p>Уже есть аккаунт? <a href="control-panel.html">Войти</a></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue