Этап 0: документация и макеты

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
andreypr207andClaude Opus 5.5 committed 2026-10-21 03:30:38 +02:00
commit 3400f58a5a
75 files changed
+14283

No files matched your search

+815
View File
@@ -0,0 +1,815 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Commissioner:wght@300;400;500;600&family=Unbounded:wght@500;600&display=swap" rel="stylesheet" />
<script>(function(){if(window.__veilixI18nSrc)return;window.__veilixI18nSrc="./i18n.js?v="+Date.now().toString(36)+Math.random().toString(36).slice(2,8);var s=document.createElement("script");s.src=window.__veilixI18nSrc;document.head.appendChild(s);})();</script>
<style>
:root{
--bg: oklch(99% 0.005 296);
--surface: oklch(100% 0 0);
--surface-2: oklch(97.5% 0.01 296);
--ink: oklch(24% 0.04 296);
--ink-2: oklch(45% 0.03 296);
--ink-3: oklch(60% 0.025 296);
--border: oklch(92% 0.014 296);
--accent: oklch(45% 0.17 296);
--accent-soft: oklch(95.5% 0.028 296);
--on-accent: oklch(99% 0.01 296);
--glow-3: oklch(91% 0.08 332);
--veil: oklch(100% 0 0 / 0.55);
--warn: oklch(62% 0.14 62);
--tg: oklch(62% 0.15 250);
}
:root[data-veilix-theme="dark"]{
--bg: oklch(17% 0.022 296);
--surface: oklch(21% 0.026 296);
--surface-2: oklch(24% 0.03 296);
--ink: oklch(96% 0.01 296);
--ink-2: oklch(79% 0.02 296);
--ink-3: oklch(63% 0.025 296);
--border: oklch(31% 0.032 296);
--accent: oklch(74% 0.15 296);
--accent-soft: oklch(27% 0.05 296);
--on-accent: oklch(17% 0.03 296);
--glow-3: oklch(42% 0.13 332);
--veil: oklch(100% 0 0 / 0.07);
--warn: oklch(78% 0.13 62);
--tg: oklch(70% 0.13 250);
}
*{ box-sizing: border-box; }
input, textarea, select{ font-size: 16px; }
[hidden]{ display: none !important; }
body{ margin:0; background: oklch(95% 0.008 296); color: var(--ink); font-family:"Commissioner", system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
:root[data-veilix-theme="dark"] body{ background: oklch(13% 0.018 296); }
a{ color: var(--accent); text-decoration: none; }
input::placeholder, textarea::placeholder{ color: var(--ink-3); }
</style>
</helmet>
<template id="__bundler_thumbnail" data-bg-color="#efeaf8">
<svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
<rect x="480" y="290" width="150" height="150" rx="48" fill="#5b32c9" />
<rect x="570" y="360" width="150" height="150" rx="48" fill="#ffffff" fill-opacity="0.62" stroke="#5b32c9" stroke-width="6" />
</svg>
</template>
<div style="min-height: 100vh; display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: center; gap: 32px; padding: 40px 24px 64px;">
<x-import component-from-global-scope="IOSDevice" from="./ios-frame.jsx?v=20260915" hint-size="393px,852px">
<div style="display: flex; flex-direction: column; height: 100%; padding-top: 62px; padding-bottom: 34px; background: var(--bg); color: var(--ink); font-family: 'Commissioner', system-ui, sans-serif; letter-spacing: -0.01em;">
<div style="display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid var(--border); background: var(--surface);">
<span style="position: relative; width: 24px; height: 24px; display: block; flex: none;">
<span style="position: absolute; left: 0; top: 3px; width: 18px; height: 18px; border-radius: 6px; background: linear-gradient(140deg, var(--accent), color-mix(in oklab, var(--accent) 55%, var(--glow-3))); display: block;"></span>
<span style="position: absolute; right: 0; bottom: 3px; width: 18px; height: 18px; border-radius: 6px; background: var(--veil); border: 1px solid color-mix(in oklab, var(--accent) 35%, transparent); display: block;"></span>
</span>
<span style="display: grid; gap: 1px; flex: 1; min-width: 0;">
<span style="font-size: 15px; font-weight: 500; letter-spacing: -0.02em;">Вейликс</span>
<span style="font-size: 11px; color: var(--tg);">мини-приложение</span>
</span>
<span style="font-size: 15px; color: var(--tg);">Закрыть</span>
</div>
<div style="flex: 1; min-height: 0; overflow: auto; padding: 16px 16px 108px;">
<div hidden="{{ buyOff }}" style="display: grid; gap: 12px;">
<div style="display: grid; gap: 4px; padding: 2px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">Ваш личный VPN</div>
<div style="font-size: 13px; color: var(--ink-2);">От 4 900 ₽ первый месяц, далее от 2 499 ₽.</div>
</div>
<div style="display: grid; gap: 9px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; gap: 10px; font-size: 14px; line-height: 1.45;">
<span style="width: 6px; height: 6px; margin-top: 7px; border-radius: 50%; background: var(--accent); display: block; flex: none;"></span>
<span>Индивидуальный IP в выбранной стране</span>
</div>
<div style="display: flex; gap: 10px; font-size: 14px; line-height: 1.45;">
<span style="width: 6px; height: 6px; margin-top: 7px; border-radius: 50%; background: var(--accent); display: block; flex: none;"></span>
<span>Выделенный RU-адрес в комплекте</span>
</div>
<div style="display: flex; gap: 10px; font-size: 14px; line-height: 1.45;">
<span style="width: 6px; height: 6px; margin-top: 7px; border-radius: 50%; background: var(--accent); display: block; flex: none;"></span>
<span>До 5 устройств</span>
</div>
<div style="display: flex; gap: 10px; font-size: 14px; line-height: 1.45;">
<span style="width: 6px; height: 6px; margin-top: 7px; border-radius: 50%; background: var(--accent); display: block; flex: none;"></span>
<span style="display: grid; gap: 5px;">
<span>2 типа подключения:</span>
<span style="display: flex; gap: 8px; color: var(--ink-2);">
<span style="flex: none;">—</span>
<span>WireGuard / Amnezia WG</span>
</span>
<span style="display: flex; gap: 8px; color: var(--ink-2);">
<span style="flex: none;">—</span>
<span>Xray: VLESS, Hysteria2, XHTTP</span>
</span>
</span>
</div>
</div>
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 8px 2px 0;">
<span style="font-size: 16px; font-weight: 500;">Локации</span>
<span style="font-size: 12px; color: var(--ink-3);">{{ locCount }}</span>
</div>
<sc-for list="{{ locations }}" as="loc" hint-placeholder-count="8">
<div style="display: grid; gap: 11px; padding: 14px; border-radius: 16px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; align-items: center; gap: 11px;">
{{ loc.flag }}
<span style="flex: 1; min-width: 0; display: grid; gap: 2px;">
<span style="font-size: 15px; font-weight: 500;">{{ loc.country }}</span>
<span style="font-size: 12px; color: {{ loc.statusInk }};">{{ loc.sub }}</span>
</span>
<button type="button" onClick="{{ loc.buy }}" style="flex: none; min-height: 44px; padding: 0 16px; border-radius: 12px; border: 0; background: {{ loc.btnBg }}; color: {{ loc.btnInk }}; font-family: inherit; font-size: 14px; font-weight: 500; cursor: pointer;">{{ loc.btn }}</button>
</div>
<div style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; padding-top: 11px; border-top: 1px solid var(--border); font-size: 12px; color: var(--ink-2); font-variant-numeric: tabular-nums;">
<span style="font-size: 15px; font-weight: 500; color: var(--ink);">{{ loc.setup }}</span>
<span>первый месяц · далее</span>
<span style="font-weight: 500; color: var(--ink);">{{ loc.month }}</span>
</div>
</div>
</sc-for>
<div style="display: grid; gap: 12px; margin-top: 8px; padding: 16px; border-radius: 18px; background: var(--surface-2); border: 1px solid var(--border);">
<div style="display: grid; gap: 5px;">
<div style="font-size: 16px; font-weight: 500;">Нужны индивидуальные условия</div>
<div style="font-size: 13px; line-height: 1.5; color: var(--ink-2);">Команда, несколько адресов, редкая страна или свой сценарий маршрутизации. Оставьте заявку — ответим в течение дня.</div>
</div>
<input type="text" placeholder="Имя" style="min-height: 46px; padding: 0 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; width: 100%;" />
<input type="text" placeholder="Telegram или почта" style="min-height: 46px; padding: 0 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; width: 100%;" />
<textarea rows="3" placeholder="Коротко о задаче" style="padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; resize: vertical; width: 100%;"></textarea>
<button type="button" onClick="{{ sendLead }}" style="min-height: 48px; border-radius: 12px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; font-weight: 500; cursor: pointer;">{{ leadButton }}</button>
</div>
</div>
<div hidden="{{ faqOff }}" style="display: grid; gap: 10px;">
<div style="display: grid; gap: 4px; padding: 2px 2px 6px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">Частые вопросы</div>
<div style="font-size: 13px; color: var(--ink-2);">Если ответа нет — напишите в поддержку.</div>
</div>
<sc-for list="{{ faq }}" as="item" hint-placeholder-count="6">
<div style="border: 1px solid var(--border); border-radius: 16px; background: var(--surface); overflow: hidden;">
<button type="button" onClick="{{ item.toggle }}" style="display: flex; align-items: center; gap: 12px; width: 100%; min-height: 52px; padding: 14px 16px; background: transparent; border: 0; color: var(--ink); font-family: inherit; font-size: 15px; font-weight: 500; text-align: left; cursor: pointer;">
<span style="flex: 1;">{{ item.q }}</span>
<span style="flex: none; width: 8px; height: 8px; border-right: 1.5px solid var(--ink-3); border-bottom: 1.5px solid var(--ink-3); transform: {{ item.rot }}; transition: transform 180ms ease; display: block;"></span>
</button>
<div hidden="{{ item.off }}" style="padding: 0 16px 16px; font-size: 14px; line-height: 1.55; color: var(--ink-2);">{{ item.a }}</div>
</div>
</sc-for>
<div style="display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 12px 16px; border-radius: 16px; border: 1px solid var(--border); background: var(--surface); font-size: 15px;">
<span style="flex: 1;">Остался вопрос</span>
<span style="color: var(--tg);">@veilix_support</span>
</div>
</div>
<div hidden="{{ keysOff }}" style="display: grid; gap: 12px;">
<div style="display: grid; gap: 4px; padding: 2px 2px 4px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">Ваши ключи</div>
<div style="font-size: 13px; color: var(--ink-2);">Нажмите, чтобы открыть подключение</div>
</div>
<sc-for list="{{ keys }}" as="key" hint-placeholder-count="2">
<button type="button" onClick="{{ key.open }}" style="display: grid; gap: 10px; width: 100%; min-height: 44px; padding: 16px; border-radius: 18px; border: 1px solid var(--border); background: var(--surface); color: var(--ink); font-family: inherit; text-align: left; cursor: pointer;">
<span style="display: flex; align-items: center; gap: 8px;">
<span style="flex: 1; font-size: 16px; font-weight: 500;">{{ key.title }}</span>
<span style="padding: 3px 9px; border-radius: 7px; background: {{ key.badgeBg }}; color: {{ key.badgeInk }}; font-size: 11px;">{{ key.badge }}</span>
</span>
<span style="display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums;">
<span>{{ key.ip }}</span>
<span>{{ key.untilLine }}</span>
</span>
</button>
</sc-for>
<button type="button" onClick="{{ goBuy }}" style="display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 50px; border-radius: 16px; border: 1px dashed var(--border); background: var(--surface-2); color: var(--ink-2); font-family: inherit; font-size: 15px; cursor: pointer;">Купить ещё один ключ</button>
</div>
<div hidden="{{ keyOff }}" style="display: grid; gap: 14px;">
<button type="button" onClick="{{ goKeys }}" style="justify-self: start; min-height: 44px; border: 0; background: transparent; color: var(--tg); font-family: inherit; font-size: 15px; cursor: pointer; padding: 0;">← Ключи</button>
<div style="display: grid; gap: 4px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">{{ cur.title }}</div>
<div style="font-size: 13px; color: var(--ink-3); font-variant-numeric: tabular-nums;">{{ cur.ip }}</div>
</div>
<div style="display: grid; gap: 10px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px;">
<span style="font-size: 13px; color: var(--ink-3);">Действует до</span>
<span style="font-size: 16px; font-weight: 500;">{{ cur.until }}</span>
</div>
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px;">
<span style="font-size: 13px; color: var(--ink-3);">Списание</span>
<span style="font-size: 15px; font-variant-numeric: tabular-nums;">2 499 ₽ · со счёта</span>
</div>
<div style="font-size: 13px; color: {{ cur.leftInk }};">{{ cur.left }}</div>
<button type="button" onClick="{{ goBilling }}" style="min-height: 44px; border-radius: 12px; border: 1px solid var(--border); background: transparent; color: var(--ink); font-family: inherit; font-size: 15px; cursor: pointer;">Счёт и платежи</button>
</div>
<div style="display: grid; gap: 10px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px;">
<span style="font-size: 16px; font-weight: 500;">WireGuard</span>
<span style="font-size: 12px; color: var(--ink-3);">5 устройств</span>
</div>
<sc-for list="{{ configs }}" as="cfg" hint-placeholder-count="5">
<div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 14px; background: var(--surface-2);">
<span style="flex: 1; min-width: 0; display: grid; gap: 2px;">
<span style="font-size: 15px;">{{ cfg.name }}</span>
<span style="font-size: 12px; color: var(--ink-3);">{{ cfg.state }}</span>
</span>
<button type="button" onClick="{{ cfg.showQr }}" style="min-height: 44px; padding: 0 14px; border-radius: 11px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 14px; cursor: pointer;">QR</button>
<button type="button" style="min-height: 44px; padding: 0 14px; border-radius: 11px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; font-size: 14px; cursor: pointer;">Файл</button>
</div>
</sc-for>
</div>
<div style="display: grid; gap: 10px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px;">
<span style="font-size: 16px; font-weight: 500;">Xray</span>
<span style="font-size: 12px; color: var(--ink-3);">Happ и другие</span>
</div>
<div style="padding: 12px; border-radius: 12px; background: var(--surface-2); font-size: 12px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ cur.xray }}</div>
<div style="display: flex; gap: 8px;">
<button type="button" onClick="{{ copyXray }}" style="flex: 1; min-height: 46px; border-radius: 12px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; cursor: pointer;">{{ copyLabel }}</button>
<button type="button" onClick="{{ showXrayQr }}" style="min-height: 46px; padding: 0 18px; border-radius: 12px; border: 1px solid var(--border); background: transparent; color: var(--ink); font-family: inherit; font-size: 15px; cursor: pointer;">QR</button>
</div>
</div>
</div>
<div hidden="{{ moreOff }}" style="display: grid; gap: 12px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em; padding: 2px;">Ещё</div>
<div style="display: grid; gap: 1px; border-radius: 18px; overflow: hidden; border: 1px solid var(--border); background: var(--border);">
<button type="button" onClick="{{ goBilling }}" style="display: flex; align-items: center; gap: 12px; min-height: 54px; padding: 12px 16px; border: 0; background: var(--surface); color: var(--ink); font-family: inherit; font-size: 15px; text-align: left; cursor: pointer;">
<span style="flex: 1;">Счёт и платежи</span>
<span style="color: var(--ink-3); font-variant-numeric: tabular-nums;">1 250 ₽</span>
<span style="color: var(--ink-3);">›</span>
</button>
<button type="button" onClick="{{ goRef }}" style="display: flex; align-items: center; gap: 12px; min-height: 54px; padding: 12px 16px; border: 0; background: var(--surface); color: var(--ink); font-family: inherit; font-size: 15px; text-align: left; cursor: pointer;">
<span style="flex: 1;">Реферальная программа</span>
<span style="color: var(--ink-3);">{{ refStats }}</span>
<span style="color: var(--ink-3);">›</span>
</button>
<button type="button" onClick="{{ goLead }}" style="display: flex; align-items: center; gap: 12px; min-height: 54px; padding: 12px 16px; border: 0; background: var(--surface); color: var(--ink); font-family: inherit; font-size: 15px; text-align: left; cursor: pointer;">
<span style="flex: 1;">Индивидуальные условия</span>
<span style="color: var(--ink-3);">›</span>
</button>
<button type="button" onClick="{{ goFaq }}" style="display: flex; align-items: center; gap: 12px; min-height: 54px; padding: 12px 16px; border: 0; background: var(--surface); color: var(--ink); font-family: inherit; font-size: 15px; text-align: left; cursor: pointer;">
<span style="flex: 1;">Частые вопросы</span>
<span style="color: var(--ink-3);">›</span>
</button>
</div>
<div style="display: grid; gap: 1px; border-radius: 18px; overflow: hidden; border: 1px solid var(--border); background: var(--border);">
<div style="display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 12px 16px; background: var(--surface); font-size: 15px;">
<span style="flex: 1;">Написать в поддержку</span>
<span style="color: var(--tg);">@veilix_support</span>
</div>
<div style="display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 12px 16px; background: var(--surface); font-size: 15px;">
<span style="flex: 1;">Почта аккаунта</span>
<span style="color: var(--ink-3);">anna@example.com</span>
</div>
</div>
<div style="display: grid; gap: 12px; padding: 14px 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px;">
<span style="font-size: 15px;">Тема</span>
<span style="display: flex; flex: none; padding: 3px; border-radius: 11px; border: 1px solid var(--border);">
<button type="button" onClick="{{ setLight }}" title="Светлая тема" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 34px; border: 0; border-radius: 8px; background: {{ lightBg }}; color: var(--ink); cursor: pointer;">
<span style="position: relative; width: 16px; height: 16px; display: block;">
<span style="position: absolute; left: 4px; top: 4px; width: 8px; height: 8px; border-radius: 50%; background: currentColor; display: block;"></span>
<span style="position: absolute; left: 0; top: 0; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid currentColor; opacity: 0.45; display: block;"></span>
</span>
</button>
<button type="button" onClick="{{ setDark }}" title="Тёмная тема" style="display: flex; align-items: center; justify-content: center; width: 40px; height: 34px; border: 0; border-radius: 8px; background: {{ darkBg }}; color: var(--ink); cursor: pointer;">
<span style="width: 16px; height: 16px; border-radius: 50%; box-shadow: inset -5px -2px 0 0 currentColor; display: block;"></span>
</button>
</span>
</div>
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 12px; border-top: 1px solid var(--border);">
<span style="font-size: 15px;">Язык</span>
<span style="display: flex; flex: none; padding: 3px; border-radius: 11px; border: 1px solid var(--border);">
<button type="button" onClick="{{ setRu }}" style="height: 34px; padding: 0 16px; border: 0; border-radius: 8px; background: {{ ruBg }}; color: {{ ruInk }}; font-family: inherit; font-size: 14px; cursor: pointer;">RU</button>
<button type="button" onClick="{{ setEn }}" style="height: 34px; padding: 0 16px; border: 0; border-radius: 8px; background: {{ enBg }}; color: {{ enInk }}; font-family: inherit; font-size: 14px; cursor: pointer;">EN</button>
</span>
</div>
<div style="font-size: 12px; color: var(--ink-3); line-height: 1.45;">По умолчанию тема подстраивается под Telegram.</div>
</div>
</div>
<div hidden="{{ billingOff }}" style="display: grid; gap: 12px;">
<button type="button" onClick="{{ goMore }}" style="justify-self: start; min-height: 44px; border: 0; background: transparent; color: var(--tg); font-family: inherit; font-size: 15px; cursor: pointer; padding: 0;">← Ещё</button>
<div style="display: grid; gap: 4px; padding: 0 2px 2px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">Счёт и платежи</div>
<div style="font-size: 13px; line-height: 1.5; color: var(--ink-2);">Карта привязана к аккаунту, а не к отдельному ключу. Списания за все ключи идут с неё или с баланса.</div>
</div>
<div style="display: grid; gap: 12px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="display: grid; gap: 3px;">
<span style="font-size: 13px; color: var(--ink-3);">Баланс</span>
<span style="font-size: 28px; font-weight: 600; letter-spacing: -0.03em; font-variant-numeric: tabular-nums;">1 250 ₽</span>
</div>
<button type="button" onClick="{{ openTopup }}" style="min-height: 48px; border-radius: 12px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; font-weight: 500; cursor: pointer;">Пополнить баланс</button>
</div>
<div hidden="{{ topupOff }}" style="display: grid; gap: 12px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--accent);">
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px;">
<span style="font-size: 16px; font-weight: 500;">Пополнение</span>
<button type="button" onClick="{{ closeTopup }}" style="border: 0; background: transparent; color: var(--ink-3); font-family: inherit; font-size: 14px; cursor: pointer; padding: 0;">Свернуть</button>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
<sc-for list="{{ sums }}" as="sum" hint-placeholder-count="3">
<button type="button" onClick="{{ sum.pick }}" style="min-height: 44px; padding: 0 16px; border-radius: 12px; border: 1px solid {{ sum.border }}; background: {{ sum.bg }}; color: var(--ink); font-family: inherit; font-size: 15px; font-variant-numeric: tabular-nums; cursor: pointer;">{{ sum.label }}</button>
</sc-for>
</div>
<div style="display: grid; gap: 6px;">
<sc-for list="{{ methods }}" as="m" hint-placeholder-count="3">
<button type="button" onClick="{{ m.pick }}" style="display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 14px; border-radius: 12px; border: 1px solid {{ m.border }}; background: {{ m.bg }}; color: var(--ink); font-family: inherit; font-size: 15px; text-align: left; cursor: pointer;">
<span style="flex: 1;">{{ m.label }}</span>
<span style="font-size: 12px; color: var(--ink-3);">{{ m.note }}</span>
</button>
</sc-for>
</div>
<div hidden="{{ cryptoOff }}" style="padding: 12px 14px; border-radius: 12px; background: var(--surface-2); font-size: 13px; line-height: 1.5; color: var(--ink-2);">USDT (TRC-20), BTC, ETH. Покажем адрес и сумму в монете, курс фиксируется на 15 минут. Баланс пополнится после одного подтверждения сети.</div>
<button type="button" onClick="{{ sendTopup }}" style="min-height: 48px; border-radius: 12px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; font-weight: 500; cursor: pointer;">{{ topupButton }}</button>
</div>
<div style="display: grid; gap: 1px; border-radius: 18px; overflow: hidden; border: 1px solid var(--border); background: var(--border);">
<div style="display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 12px 16px; background: var(--surface); font-size: 15px;">
<span style="flex: 1;">Карта аккаунта</span>
<span style="color: var(--ink-3); font-variant-numeric: tabular-nums;">···· 4242</span>
</div>
<div style="display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 12px 16px; background: var(--surface); font-size: 15px;">
<span style="flex: 1;">Следующее списание</span>
<span style="color: var(--ink-3); font-variant-numeric: tabular-nums;">2 499 ₽ · 21 сент.</span>
</div>
</div>
<div style="font-size: 16px; font-weight: 500; padding: 8px 2px 0;">История платежей</div>
<sc-for list="{{ payments }}" as="p" hint-placeholder-count="5">
<div style="display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: 16px; background: var(--surface); border: 1px solid var(--border);">
<span style="flex: 1; min-width: 0; display: grid; gap: 3px;">
<span style="font-size: 14px;">{{ p.title }}</span>
<span style="font-size: 12px; color: var(--ink-3);">{{ p.meta }}</span>
</span>
<span style="flex: none; font-size: 15px; font-weight: 500; color: {{ p.ink }}; font-variant-numeric: tabular-nums;">{{ p.amount }}</span>
</div>
</sc-for>
</div>
<div hidden="{{ refOff }}" style="display: grid; gap: 12px;">
<button type="button" onClick="{{ goMore }}" style="justify-self: start; min-height: 44px; border: 0; background: transparent; color: var(--tg); font-family: inherit; font-size: 15px; cursor: pointer; padding: 0;">← Ещё</button>
<div style="display: grid; gap: 4px; padding: 0 2px 2px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">Реферальная программа</div>
<div style="font-size: 13px; line-height: 1.5; color: var(--ink-2);">Приведите друга — вы оба получите по месяцу в подарок.</div>
</div>
<div style="display: flex; gap: 8px;">
<div style="flex: 1; display: grid; gap: 4px; padding: 14px; border-radius: 16px; background: var(--surface); border: 1px solid var(--border);">
<span style="font-size: 12px; color: var(--ink-3);">Приглашено</span>
<span style="font-size: 24px; font-weight: 600; letter-spacing: -0.03em; font-variant-numeric: tabular-nums;">3</span>
</div>
<div style="flex: 1; display: grid; gap: 4px; padding: 14px; border-radius: 16px; background: var(--surface); border: 1px solid var(--border);">
<span style="font-size: 12px; color: var(--ink-3);">Месяцев</span>
<span style="font-size: 24px; font-weight: 600; letter-spacing: -0.03em; font-variant-numeric: tabular-nums;">2</span>
</div>
</div>
<div style="display: grid; gap: 10px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<div style="font-size: 15px; font-weight: 500;">Ваша ссылка</div>
<div style="padding: 12px; border-radius: 12px; background: var(--surface-2); font-size: 13px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">t.me/veilix_bot?start=anna7f2c</div>
<div style="display: flex; gap: 8px;">
<button type="button" onClick="{{ copyRef }}" style="flex: 1; min-height: 46px; border-radius: 12px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; cursor: pointer;">{{ refLabel }}</button>
<button type="button" style="min-height: 46px; padding: 0 18px; border-radius: 12px; border: 1px solid var(--border); background: transparent; color: var(--ink); font-family: inherit; font-size: 15px; cursor: pointer;">Поделиться</button>
</div>
<div style="font-size: 12px; line-height: 1.45; color: var(--ink-3);">Месяц начисляется после первой оплаты друга и применяется к ближайшему списанию.</div>
</div>
<div style="display: grid; gap: 1px; border-radius: 18px; overflow: hidden; border: 1px solid var(--border); background: var(--border);">
<div style="display: flex; align-items: center; gap: 12px; min-height: 50px; padding: 12px 16px; background: var(--surface); font-size: 14px;">
<span style="flex: 1;">m···@gmail.com</span>
<span style="color: var(--accent);">Месяц начислен</span>
</div>
<div style="display: flex; align-items: center; gap: 12px; min-height: 50px; padding: 12px 16px; background: var(--surface); font-size: 14px;">
<span style="flex: 1;">d···@yandex.ru</span>
<span style="color: var(--accent);">Месяц начислен</span>
</div>
<div style="display: flex; align-items: center; gap: 12px; min-height: 50px; padding: 12px 16px; background: var(--surface); font-size: 14px;">
<span style="flex: 1;">k···@icloud.com</span>
<span style="color: var(--ink-3);">Ждём оплаты</span>
</div>
</div>
</div>
<div hidden="{{ leadOff }}" style="display: grid; gap: 12px;">
<button type="button" onClick="{{ goMore }}" style="justify-self: start; min-height: 44px; border: 0; background: transparent; color: var(--tg); font-family: inherit; font-size: 15px; cursor: pointer; padding: 0;">← Ещё</button>
<div style="display: grid; gap: 4px; padding: 0 2px 2px;">
<div style="font-size: 22px; font-weight: 600; letter-spacing: -0.03em;">Индивидуальные условия</div>
<div style="font-size: 13px; line-height: 1.5; color: var(--ink-2);">Команда, несколько адресов, редкая страна или свой сценарий маршрутизации. Ответим в течение дня.</div>
</div>
<div style="display: grid; gap: 10px; padding: 16px; border-radius: 18px; background: var(--surface); border: 1px solid var(--border);">
<input type="text" placeholder="Имя" style="min-height: 46px; padding: 0 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; width: 100%;" />
<input type="text" placeholder="Telegram или почта" style="min-height: 46px; padding: 0 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; width: 100%;" />
<input type="text" placeholder="Сколько адресов и в каких странах" style="min-height: 46px; padding: 0 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; width: 100%;" />
<textarea rows="4" placeholder="Коротко о задаче" style="padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-family: inherit; resize: vertical; width: 100%;"></textarea>
<button type="button" onClick="{{ sendLead }}" style="min-height: 48px; border-radius: 12px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; font-weight: 500; cursor: pointer;">{{ leadButton }}</button>
</div>
</div>
</div>
<div style="position: absolute; left: 50%; bottom: 46px; transform: translateX(-50%); z-index: 15; display: flex; gap: 2px; padding: 6px; border-radius: 999px; background: color-mix(in oklab, var(--surface) 62%, transparent); backdrop-filter: blur(24px) saturate(1.8); -webkit-backdrop-filter: blur(24px) saturate(1.8); border: 1px solid color-mix(in oklab, var(--ink) 8%, transparent); box-shadow: 0 10px 30px -10px color-mix(in oklab, var(--ink) 40%, transparent);">
<button type="button" onClick="{{ goBuy }}" style="display: grid; justify-items: center; gap: 4px; min-width: 72px; min-height: 52px; padding: 8px 10px; border: 0; border-radius: 999px; background: {{ tabBuyBg }}; color: {{ tabBuyInk }}; font-family: inherit; font-size: 11px; cursor: pointer;">
<span style="position: relative; width: 20px; height: 20px; display: block;">
<span style="position: absolute; left: 8px; top: 0; width: 4px; height: 20px; border-radius: 2px; background: currentColor; display: block;"></span>
<span style="position: absolute; left: 0; top: 8px; width: 20px; height: 4px; border-radius: 2px; background: currentColor; display: block;"></span>
</span>
Купить
</button>
<button type="button" onClick="{{ goFaq }}" style="display: grid; justify-items: center; gap: 4px; min-width: 72px; min-height: 52px; padding: 8px 10px; border: 0; border-radius: 999px; background: {{ tabFaqBg }}; color: {{ tabFaqInk }}; font-family: inherit; font-size: 11px; cursor: pointer;">
<span style="display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; border: 2px solid currentColor; font-size: 13px; font-weight: 600; line-height: 1;">?</span>
FAQ
</button>
<button type="button" onClick="{{ goKeys }}" style="display: grid; justify-items: center; gap: 4px; min-width: 72px; min-height: 52px; padding: 8px 10px; border: 0; border-radius: 999px; background: {{ tabKeysBg }}; color: {{ tabKeysInk }}; font-family: inherit; font-size: 11px; cursor: pointer;">
<span style="position: relative; width: 20px; height: 20px; display: block;">
<span style="position: absolute; left: 0; top: 2px; width: 20px; height: 4px; border-radius: 2px; background: currentColor; display: block;"></span>
<span style="position: absolute; left: 0; top: 9px; width: 20px; height: 4px; border-radius: 2px; background: currentColor; opacity: 0.6; display: block;"></span>
<span style="position: absolute; left: 0; top: 16px; width: 20px; height: 4px; border-radius: 2px; background: currentColor; opacity: 0.3; display: block;"></span>
</span>
Ключи
</button>
<button type="button" onClick="{{ goMore }}" style="display: grid; justify-items: center; gap: 4px; min-width: 72px; min-height: 52px; padding: 8px 10px; border: 0; border-radius: 999px; background: {{ tabMoreBg }}; color: {{ tabMoreInk }}; font-family: inherit; font-size: 11px; cursor: pointer;">
<span style="position: relative; width: 20px; height: 20px; display: block;">
<span style="position: absolute; left: 0; top: 8px; width: 4px; height: 4px; border-radius: 50%; background: currentColor; display: block;"></span>
<span style="position: absolute; left: 8px; top: 8px; width: 4px; height: 4px; border-radius: 50%; background: currentColor; display: block;"></span>
<span style="position: absolute; left: 16px; top: 8px; width: 4px; height: 4px; border-radius: 50%; background: currentColor; display: block;"></span>
</span>
Ещё
</button>
</div>
<div hidden="{{ payOff }}" onClick="{{ closePay }}" style="position: absolute; left: 0; right: 0; top: -62px; bottom: -34px; z-index: 22; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 34px; background: color-mix(in oklab, var(--ink) 45%, transparent);">
<div onClick="{{ stopPay }}" style="width: 100%; display: grid; gap: 14px; padding: 22px 20px 34px; border-radius: 22px 22px 0 0; background: var(--surface); border-top: 1px solid var(--border);">
<span style="justify-self: center; width: 36px; height: 4px; border-radius: 2px; background: var(--border); display: block;"></span>
<div style="display: flex; align-items: center; gap: 12px;">
{{ payFlag }}
<span style="flex: 1; min-width: 0; display: grid; gap: 2px;">
<span style="font-size: 17px; font-weight: 600; letter-spacing: -0.02em;">{{ payTitle }}</span>
<span style="font-size: 12px; color: var(--accent);">{{ paySub }}</span>
</span>
</div>
<div style="display: grid; gap: 8px; padding: 14px 16px; border-radius: 14px; background: var(--surface-2);">
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 14px;">
<span style="color: var(--ink-2);">Первый месяц</span>
<span style="font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums;">{{ paySetup }}</span>
</div>
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 14px;">
<span style="color: var(--ink-2);">Далее каждый месяц</span>
<span style="font-variant-numeric: tabular-nums;">{{ payMonth }}</span>
</div>
<div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 14px; padding-top: 8px; border-top: 1px solid var(--border);">
<span style="color: var(--ink-2);">В комплекте</span>
<span>RU-адрес · 5 устройств</span>
</div>
</div>
<button type="button" onClick="{{ confirmPay }}" style="min-height: 52px; border-radius: 14px; border: 0; background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 16px; font-weight: 500; cursor: pointer;">{{ payButton }}</button>
<div style="font-size: 12px; line-height: 1.45; color: var(--ink-3); text-align: center;">Оплата пройдёт внутри Telegram. Ключ появится во вкладке «Ключи» сразу после подтверждения.</div>
</div>
</div>
<div hidden="{{ qrOff }}" onClick="{{ closeQr }}" style="position: absolute; left: 0; right: 0; top: -62px; bottom: -34px; z-index: 20; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 34px; background: color-mix(in oklab, var(--ink) 45%, transparent);">
<div style="width: 100%; display: grid; gap: 14px; justify-items: center; padding: 22px 20px 34px; border-radius: 22px 22px 0 0; background: var(--surface); border-top: 1px solid var(--border);">
<span style="width: 36px; height: 4px; border-radius: 2px; background: var(--border); display: block;"></span>
<span style="font-size: 16px; font-weight: 500;">{{ qrTitle }}</span>
{{ qrImage }}
<span style="font-size: 12px; color: var(--ink-3); text-align: center; max-width: 240px; line-height: 1.45;">Наведите камеру телефона на код в приложении WireGuard или Happ.</span>
</div>
</div>
</div>
</x-import>
<div style="max-width: 320px; display: grid; gap: 14px; align-content: start; padding-top: 12px;">
<div style="font-size: 20px; font-weight: 600; letter-spacing: -0.03em; color: var(--ink);">Мини-приложение в Telegram</div>
<p style="margin: 0; font-size: 15px; line-height: 1.55; color: var(--ink-2); font-weight: 300;">В Telegram приложение работает и как витрина, и как кабинет: человек может попасть сюда, ни разу не открыв сайт. Поэтому первая вкладка — «Купить»: тезисы, все локации с ценами и кнопкой покупки у каждой, форма на индивидуальные условия внизу.</p>
<p style="margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink-3); font-weight: 300;">Вкладки: Купить, FAQ, Ключи, Ещё. В «Ещё» — счёт и платежи, реферальная программа, индивидуальный заказ, тема и язык.</p>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;defaultTheme&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;light&quot;,&quot;dark&quot;,&quot;system&quot;],&quot;default&quot;:&quot;light&quot;,&quot;tsType&quot;:&quot;'light' | 'dark' | 'system'&quot;,&quot;section&quot;:&quot;Тема&quot;}}">
class Component extends DCLogic {
state = { theme: null, sysDark: false, view: "buy", key: 0, qr: null, copied: false, lang: "ru", refCopied: false, faq: -1, leadSent: false, topup: false, sum: 1, method: 0, topupSent: false, pay: null, paid: false };
componentDidMount() {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
this.mq = mq;
this.onSys = () => this.setState({ sysDark: mq.matches });
mq.addEventListener("change", this.onSys);
let saved = null;
try { saved = localStorage.getItem("veilix.theme"); } catch (e) {}
let savedLang = null;
try { savedLang = localStorage.getItem("veilix.lang"); } catch (e) {}
this.setState({ sysDark: mq.matches, theme: saved || this.props.defaultTheme || "light", lang: savedLang || "ru" }, () => { this.apply(); this.applyLang(); });
}
applyLang() {
const lang = this.state.lang || "ru";
if (window.VEILIX_APPLY_LANG) window.VEILIX_APPLY_LANG(document.body, lang);
else setTimeout(() => this.applyLang(), 150);
}
setLang(l) {
try { localStorage.setItem("veilix.lang", l); } catch (e) {}
this.setState({ lang: l }, () => this.applyLang());
}
componentWillUnmount() { if (this.mq) this.mq.removeEventListener("change", this.onSys); }
componentDidUpdate() { this.apply(); this.applyLang(); }
apply() {
const theme = this.state.theme || this.props.defaultTheme || "light";
const dark = theme === "dark" || (theme === "system" && this.state.sysDark);
document.documentElement.setAttribute("data-veilix-theme", dark ? "dark" : "light");
}
setTheme(t) {
try { localStorage.setItem("veilix.theme", t); } catch (e) {}
this.setState({ theme: t }, () => this.apply());
}
data() {
const base = { setup: "4 900 ₽", month: "2 499 ₽" };
const rare = { setup: "6 900 ₽", month: "3 499 ₽" };
const far = { setup: "9 900 ₽", month: "4 999 ₽" };
const L = (country, city, code, servers, tier) => ({ country, city, code, servers, setup: tier.setup, month: tier.month });
return [
L("Нидерланды", "Амстердам", "nl", 12, base),
L("Германия", "Франкфурт", "de", 10, base),
L("США", "Нью-Йорк", "us", 9, base),
L("Великобритания", "Лондон", "gb", 8, base),
L("Франция", "Париж", "fr", 7, base),
L("Финляндия", "Хельсинки", "fi", 6, base),
L("Швеция", "Стокгольм", "se", 5, base),
L("Норвегия", "Осло", "no", 0, base),
L("Дания", "Копенгаген", "dk", 0, base),
L("Швейцария", "Цюрих", "ch", 4, rare),
L("Австрия", "Вена", "at", 5, base),
L("Испания", "Мадрид", "es", 6, base),
L("Италия", "Милан", "it", 6, base),
L("Польша", "Варшава", "pl", 7, base),
L("Чехия", "Прага", "cz", 5, base),
L("Румыния", "Бухарест", "ro", 0, base),
L("Литва", "Вильнюс", "lt", 4, base),
L("Латвия", "Рига", "lv", 4, base),
L("Эстония", "Таллин", "ee", 0, base),
L("Турция", "Стамбул", "tr", 6, base),
L("Казахстан", "Алматы", "kz", 0, base),
L("Армения", "Ереван", "am", 0, base),
L("Грузия", "Тбилиси", "ge", 0, base),
L("ОАЭ", "Дубай", "ae", 3, rare),
L("Сингапур", "Сингапур", "sg", 4, rare),
L("Япония", "Токио", "jp", 4, rare),
L("Южная Корея", "Сеул", "kr", 0, rare),
L("Канада", "Торонто", "ca", 6, base),
L("Австралия", "Сидней", "au", 0, far),
L("Бразилия", "Сан-Паулу", "br", 0, far)
];
}
flagSpecs() {
return {
nl: { rows: ["#AE1C28", "#FFFFFF", "#21468B"] },
de: { rows: ["#000000", "#DD0000", "#FFCE00"] },
us: { rows: ["#B22234", "#FFFFFF", "#B22234", "#FFFFFF", "#B22234", "#FFFFFF"], marks: [{ l: 0, t: 0, w: 19, h: 15, bg: "#3C3B6E" }] },
gb: { bg: "#012169", marks: [{ l: 15, t: 0, w: 10, h: 28, bg: "#FFFFFF" }, { l: 0, t: 9, w: 40, h: 10, bg: "#FFFFFF" }, { l: 17, t: 0, w: 5, h: 28, bg: "#C8102E" }, { l: 0, t: 11, w: 40, h: 5, bg: "#C8102E" }] },
fr: { cols: ["#002395", "#FFFFFF", "#ED2939"] },
fi: { bg: "#FFFFFF", marks: [{ l: 11, t: 0, w: 7, h: 28, bg: "#003580" }, { l: 0, t: 10, w: 40, h: 7, bg: "#003580" }] },
se: { bg: "#006AA7", marks: [{ l: 11, t: 0, w: 5, h: 28, bg: "#FECC00" }, { l: 0, t: 11, w: 40, h: 5, bg: "#FECC00" }] },
no: { bg: "#BA0C2F", marks: [{ l: 10, t: 0, w: 9, h: 28, bg: "#FFFFFF" }, { l: 0, t: 9, w: 40, h: 9, bg: "#FFFFFF" }, { l: 13, t: 0, w: 3, h: 28, bg: "#00205B" }, { l: 0, t: 12, w: 40, h: 3, bg: "#00205B" }] },
dk: { bg: "#C8102E", marks: [{ l: 11, t: 0, w: 5, h: 28, bg: "#FFFFFF" }, { l: 0, t: 11, w: 40, h: 5, bg: "#FFFFFF" }] },
ch: { bg: "#D52B1E", marks: [{ l: 17, t: 7, w: 5, h: 14, bg: "#FFFFFF" }, { l: 13, t: 11, w: 14, h: 5, bg: "#FFFFFF" }] },
at: { rows: ["#ED2939", "#FFFFFF", "#ED2939"] },
es: { rows: ["#AA151B", "#F1BF00", "#AA151B"], sizes: "1fr 2fr 1fr" },
it: { cols: ["#008C45", "#F4F5F0", "#CD212A"] },
pl: { rows: ["#FFFFFF", "#DC143C"] },
cz: { rows: ["#FFFFFF", "#D7141A"], marks: [{ l: 0, t: 0, w: 16, h: 28, bg: "#11457E", clip: "polygon(0 0, 100% 50%, 0 100%)" }] },
ro: { cols: ["#002B7F", "#FCD116", "#CE1126"] },
lt: { rows: ["#FDB913", "#006A44", "#C1272D"] },
lv: { rows: ["#9E3039", "#FFFFFF", "#9E3039"], sizes: "2fr 1fr 2fr" },
ee: { rows: ["#0072CE", "#000000", "#FFFFFF"] },
tr: { bg: "#E30A17", marks: [{ l: 9, t: 7, w: 14, h: 14, r: "50%", bg: "#FFFFFF" }, { l: 13, t: 7, w: 14, h: 14, r: "50%", bg: "#E30A17" }] },
kz: { bg: "#00AFCA", marks: [{ l: 14, t: 7, w: 13, h: 13, r: "50%", bg: "#FEC50C" }] },
am: { rows: ["#D90012", "#0033A0", "#F2A800"] },
ge: { bg: "#FFFFFF", marks: [{ l: 17, t: 0, w: 5, h: 28, bg: "#FF0000" }, { l: 0, t: 11, w: 40, h: 5, bg: "#FF0000" }] },
ae: { rows: ["#00732F", "#FFFFFF", "#000000"], marks: [{ l: 0, t: 0, w: 10, h: 28, bg: "#FF0000" }] },
sg: { rows: ["#EF3340", "#FFFFFF"], marks: [{ l: 4, t: 3, w: 10, h: 10, r: "50%", bg: "#FFFFFF" }, { l: 8, t: 3, w: 10, h: 10, r: "50%", bg: "#EF3340" }] },
jp: { bg: "#FFFFFF", marks: [{ l: 14, t: 8, w: 12, h: 12, r: "50%", bg: "#BC002D" }] },
kr: { bg: "#FFFFFF", marks: [{ l: 14, t: 8, w: 12, h: 12, r: "50%", bg: "#CD2E3A" }, { l: 14, t: 8, w: 12, h: 12, r: "50%", bg: "#0047A0", clip: "polygon(0 50%, 100% 50%, 100% 100%, 0 100%)" }] },
ca: { cols: ["#D80621", "#FFFFFF", "#D80621"], sizes: "1fr 2fr 1fr", marks: [{ l: 17, t: 8, w: 6, h: 12, bg: "#D80621", clip: "polygon(50% 0, 100% 100%, 0 100%)" }] },
au: { bg: "#00247D", marks: [{ l: 8, t: 0, w: 3, h: 14, bg: "#FFFFFF" }, { l: 0, t: 5, w: 19, h: 3, bg: "#FFFFFF" }, { l: 26, t: 17, w: 6, h: 6, r: "50%", bg: "#FFFFFF" }] },
br: { bg: "#009739", marks: [{ l: 8, t: 3, w: 24, h: 22, bg: "#FEDD00", clip: "polygon(50% 0, 100% 50%, 50% 100%, 0 50%)" }, { l: 15, t: 10, w: 9, h: 9, r: "50%", bg: "#012169" }] }
};
}
flag(code) {
const h = React.createElement;
const spec = this.flagSpecs()[code] || {};
const style = { width: "40px", height: "28px", borderRadius: "5px", overflow: "hidden", display: "grid", border: "1px solid var(--border)", position: "relative", flex: "none" };
let bands = [];
if (spec.rows) {
bands = spec.rows;
style.gridTemplateRows = spec.sizes || "repeat(" + bands.length + ", 1fr)";
} else if (spec.cols) {
bands = spec.cols;
style.gridAutoFlow = "column";
style.gridTemplateColumns = spec.sizes || "repeat(" + bands.length + ", 1fr)";
} else {
style.background = spec.bg || "var(--surface-2)";
}
const kids = bands.map((c, i) => h("span", { key: "b" + i, style: { background: c, display: "block" } }));
(spec.marks || []).forEach((m, i) => kids.push(h("span", {
key: "m" + i,
style: { position: "absolute", left: m.l + "px", top: m.t + "px", width: m.w + "px", height: m.h + "px", borderRadius: m.r || 0, background: m.bg, clipPath: m.clip || "none", display: "block" }
})));
return h("span", { style: style }, kids);
}
qrImage(seed) {
const n = 21;
let h = 0;
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
const cells = [];
const finder = (r, c) => (r < 7 && c < 7) || (r < 7 && c >= n - 7) || (r >= n - 7 && c < 7);
for (let r = 0; r < n; r++) {
for (let c = 0; c < n; c++) {
let on;
if (finder(r, c)) {
const rr = r < 7 ? r : n - 1 - r;
const cc = c < 7 ? c : n - 1 - c;
on = Math.max(Math.abs(rr - 3), Math.abs(cc - 3)) !== 2;
} else {
h = (h * 1103515245 + 12345) >>> 0;
on = ((h >>> 16) & 1) === 1;
}
cells.push(React.createElement("span", { key: r + "-" + c, style: { background: on ? "var(--ink)" : "transparent", display: "block" } }));
}
}
return React.createElement("div", {
style: { display: "grid", gridTemplateColumns: "repeat(" + n + ", 1fr)", width: "196px", height: "196px", padding: "10px", borderRadius: "14px", background: "var(--bg)", border: "1px solid var(--border)" }
}, cells);
}
plural(n) {
const m10 = n % 10, m100 = n % 100;
if (m10 === 1 && m100 !== 11) return "сервер";
if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) return "сервера";
return "серверов";
}
renderVals() {
const s = this.state;
const sel = "var(--accent-soft)";
const theme = s.theme || this.props.defaultTheme || "light";
const keyData = [
{ title: "Нидерланды · Амстердам", ip: "185.72.14.203", until: s.lang === "en" ? "14 October" : "14 октября", badge: "Активен", warn: false, left: "Продлится автоматически через 31 день", xray: "vless://8f2a…@nl1.veilix.net:443?type=xhttp#Veilix-NL" },
{ title: "США · Нью-Йорк", ip: "23.105.61.44", until: s.lang === "en" ? "21 September" : "21 сентября", badge: "Истекает", warn: true, left: "Осталось 8 дней", xray: "vless://c41d…@us1.veilix.net:443?type=xhttp#Veilix-US" }
];
const cur = keyData[s.key];
const cfgNames = [["Ноутбук", "подключено"], ["Телефон", "подключено"], ["Планшет", "подключено"], ["Рабочий ноутбук", "не занят"], ["Свободный слот", "не занят"]];
const faqData = [
["Чем это отличается от обычного VPN?", "У вас личный IP-адрес, который не делят с другими. Его не банят за чужую активность, и сервисы видят вас как обычного жителя выбранной страны."],
["Что входит в цену?", "Индивидуальный IP в выбранной стране, выделенный RU-адрес, до 5 устройств, WireGuard и Amnezia WG, а также Xray с VLESS, Hysteria2 и XHTTP."],
["Сколько устройств можно подключить?", "До пяти. На каждое — своя конфигурация WireGuard, IP-адрес при этом один и тот же."],
["Как быстро выдаётся доступ?", "Если в локации есть свободные серверы — сразу после оплаты. Если нет, поднимем сервер под вас в течение 24 часов."],
["Что если страна перестала подходить?", "Напишите в поддержку: перенесём ключ в другую локацию, доплата только за разницу в тарифе."],
["Как платить и как отменить?", "Карта привязана к аккаунту, можно держать баланс и пополнять его картой, через СБП или криптой. Отмена — в разделе «Счёт и платежи», ключ доработает оплаченный месяц."]
];
return {
locCount: s.lang === "en" ? "30 countries" : "30 стран",
locations: this.data().map((d, i) => ({
country: d.country,
flag: this.flag(d.code),
sub: d.servers > 0 ? d.city + " · " + d.servers + " " + this.plural(d.servers) + " свободно" : d.city + " · установим за 24 часа",
statusInk: d.servers > 0 ? "var(--accent)" : "var(--ink-3)",
setup: d.setup,
month: d.month,
btn: d.servers > 0 ? "Купить" : "Заказать",
btnBg: d.servers > 0 ? "var(--accent)" : "var(--surface-2)",
btnInk: d.servers > 0 ? "var(--on-accent)" : "var(--ink-2)",
buy: () => d.servers > 0 ? this.setState({ pay: d, paid: false }) : this.setState({ view: "lead" })
})),
faq: faqData.map((row, i) => ({
q: row[0],
a: row[1],
off: s.faq !== i,
rot: s.faq === i ? "rotate(-135deg) translateY(2px)" : "rotate(45deg) translateY(-2px)",
toggle: () => this.setState(st => ({ faq: st.faq === i ? -1 : i }))
})),
keys: keyData.map((k, i) => ({
title: k.title,
ip: k.ip,
untilLine: (s.lang === "en" ? "until " : "до ") + k.until,
badge: k.badge,
badgeBg: k.warn ? "color-mix(in oklab, var(--warn) 18%, transparent)" : sel,
badgeInk: k.warn ? "var(--warn)" : "var(--accent)",
open: () => this.setState({ view: "key", key: i })
})),
cur: {
title: cur.title, ip: cur.ip, until: cur.until,
left: cur.left, leftInk: cur.warn ? "var(--warn)" : "var(--ink-2)", xray: cur.xray
},
configs: cfgNames.map(c => ({
name: c[0],
state: c[1],
showQr: () => this.setState({ qr: { title: "WireGuard · " + c[0], seed: cur.ip + c[0] } })
})),
goBuy: () => this.setState({ view: "buy" }),
goFaq: () => this.setState({ view: "faq" }),
goKeys: () => this.setState({ view: "keys" }),
goMore: () => this.setState({ view: "more" }),
goRef: () => this.setState({ view: "ref" }),
goBilling: () => this.setState({ view: "billing" }),
goLead: () => this.setState({ view: "lead" }),
buyOff: s.view !== "buy",
faqOff: s.view !== "faq",
keysOff: s.view !== "keys",
keyOff: s.view !== "key",
moreOff: s.view !== "more",
refOff: s.view !== "ref",
billingOff: s.view !== "billing",
leadOff: s.view !== "lead",
tabBuyBg: s.view === "buy" ? sel : "transparent",
tabFaqBg: s.view === "faq" ? sel : "transparent",
tabKeysBg: (s.view === "keys" || s.view === "key") ? sel : "transparent",
tabMoreBg: (s.view === "more" || s.view === "ref" || s.view === "billing" || s.view === "lead") ? sel : "transparent",
tabBuyInk: s.view === "buy" ? "var(--accent)" : "var(--ink-2)",
tabFaqInk: s.view === "faq" ? "var(--accent)" : "var(--ink-2)",
tabKeysInk: (s.view === "keys" || s.view === "key") ? "var(--accent)" : "var(--ink-2)",
tabMoreInk: (s.view === "more" || s.view === "ref" || s.view === "billing" || s.view === "lead") ? "var(--accent)" : "var(--ink-2)",
openTopup: () => this.setState({ topup: true }),
closeTopup: () => this.setState({ topup: false }),
topupOff: !s.topup,
sums: ["2 500 ₽", "5 000 ₽", "10 000 ₽"].map((label, i) => ({
label: label,
bg: s.sum === i ? sel : "transparent",
border: s.sum === i ? "var(--accent)" : "var(--border)",
pick: () => this.setState({ sum: i })
})),
methods: [
{ label: "Карта ···· 4242", note: "мгновенно" },
{ label: "СБП", note: "мгновенно" },
{ label: "Криптовалюта", note: "USDT, BTC, ETH" }
].map((m, i) => ({
label: m.label,
note: m.note,
bg: s.method === i ? sel : "transparent",
border: s.method === i ? "var(--accent)" : "var(--border)",
pick: () => this.setState({ method: i })
})),
cryptoOff: s.method !== 2,
sendTopup: () => this.setState({ topupSent: true }),
topupButton: s.topupSent ? "Счёт выставлен" : "Пополнить",
payments: [
{ title: "Продление · Нидерланды", meta: "14 сентября · карта ···· 4242", amount: "−2 499 ₽", ink: "var(--ink)" },
{ title: "Пополнение баланса", meta: "6 сентября · USDT (TRC-20)", amount: "+5 000 ₽", ink: "var(--accent)" },
{ title: "Первый месяц · США", meta: "21 августа · карта ···· 4242", amount: "−4 900 ₽", ink: "var(--ink)" },
{ title: "Продление · Нидерланды", meta: "14 августа · с баланса", amount: "−2 499 ₽", ink: "var(--ink)" },
{ title: "Реферальный бонус", meta: "2 августа · месяц в подарок", amount: "+2 499 ₽", ink: "var(--accent)" }
],
sendLead: () => this.setState({ leadSent: true }),
leadButton: s.leadSent ? "Заявка отправлена" : "Отправить заявку",
refStats: s.lang === "en" ? "3 invited" : "3 друга",
copyRef: () => this.setState({ refCopied: true }),
refLabel: s.refCopied ? "Ссылка скопирована" : "Копировать ссылку",
copyXray: () => this.setState({ copied: true }),
copyLabel: s.copied ? "Скопировано" : "Копировать ключ",
showXrayQr: () => this.setState({ qr: { title: "Xray · Happ", seed: cur.xray } }),
payOff: !s.pay,
payFlag: s.pay ? this.flag(s.pay.code) : null,
payTitle: s.pay ? s.pay.country + " · " + s.pay.city : "",
paySub: s.pay ? s.pay.servers + " " + this.plural(s.pay.servers) + " свободно · доступ сразу" : "",
paySetup: s.pay ? s.pay.setup : "",
payMonth: s.pay ? s.pay.month : "",
payButton: s.paid ? "Оплата подтверждена" : (s.pay ? "Оплатить " + s.pay.setup : "Оплатить"),
confirmPay: () => this.setState({ paid: true }),
closePay: () => this.setState({ pay: null, paid: false }),
stopPay: (e) => e.stopPropagation(),
qrOff: !s.qr,
qrTitle: s.qr ? s.qr.title : "",
qrImage: s.qr ? this.qrImage(s.qr.seed) : null,
closeQr: () => this.setState({ qr: null }),
setRu: () => this.setLang("ru"),
setEn: () => this.setLang("en"),
ruBg: s.lang === "en" ? "transparent" : sel,
enBg: s.lang === "en" ? sel : "transparent",
ruInk: s.lang === "en" ? "var(--ink-3)" : "var(--ink)",
enInk: s.lang === "en" ? "var(--ink)" : "var(--ink-3)",
setLight: () => this.setTheme("light"),
setDark: () => this.setTheme("dark"),
lightBg: theme === "light" ? sel : "transparent",
darkBg: theme === "dark" ? sel : "transparent"
};
}
}
</script>
</body>
</html>