From c1b7ace876e99fdbe93d19973c4502861f9d9905 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Mon, 27 Apr 2026 09:42:18 +0300 Subject: [PATCH] fix(webapp): merge bundled FALLBACK_I18N with server locales The bot's locales/*.json files do not contain the webapp-specific keys (connect, extend_subscription, loading, etc.), so applyI18n was overwriting the localized HTML defaults with raw key names. Merge the bundled FALLBACK_I18N table with whatever the server provides per language, letting server values override but falling back to the in-bundle translations for keys the bot does not ship. --- bot/app/web/templates/subscription_webapp.js | 10 +++++++++- ...723dfe05.js => subscription_webapp.min.4b826583.js} | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) rename bot/app/web/templates/{subscription_webapp.min.723dfe05.js => subscription_webapp.min.4b826583.js} (62%) diff --git a/bot/app/web/templates/subscription_webapp.js b/bot/app/web/templates/subscription_webapp.js index a26619a..0dc5c77 100644 --- a/bot/app/web/templates/subscription_webapp.js +++ b/bot/app/web/templates/subscription_webapp.js @@ -436,7 +436,15 @@ const MOCK = (() => { referral_bonus_explanation: 'Bonuses are awarded once for each invited user when they purchase a subscription.' } }; - const I18N = readJsonScript('i18n') || (MOCK && MOCK.i18n) || {}; + const I18N = (function () { + const server = readJsonScript('i18n') || (MOCK && MOCK.i18n) || {}; + const merged = {}; + const langs = new Set(Object.keys(FALLBACK_I18N).concat(Object.keys(server))); + langs.forEach(function (lang) { + merged[lang] = Object.assign({}, FALLBACK_I18N[lang] || {}, server[lang] || {}); + }); + return merged; + })(); const accent = CFG.primaryColor || '#00fe7a'; document.documentElement.style.setProperty('--accent', accent); diff --git a/bot/app/web/templates/subscription_webapp.min.723dfe05.js b/bot/app/web/templates/subscription_webapp.min.4b826583.js similarity index 62% rename from bot/app/web/templates/subscription_webapp.min.723dfe05.js rename to bot/app/web/templates/subscription_webapp.min.4b826583.js index 3c6b3bf..245a530 100644 --- a/bot/app/web/templates/subscription_webapp.min.723dfe05.js +++ b/bot/app/web/templates/subscription_webapp.min.4b826583.js @@ -1,4 +1,4 @@ -const MOCK=(()=>{const e=window.__WEBAPP_DEV_MOCK__,n=window.location.hostname,a=window.location.protocol==="file:"||n==="localhost"||n==="127.0.0.1"||n==="";return e&&a?e:null})();function readJsonScript(e){const n=document.getElementById(e);if(!n||!n.textContent)return null;try{return JSON.parse(n.textContent)}catch(a){return console.warn("Failed to parse JSON config from #"+e,a),null}}const CFG=readJsonScript("webapp-config")||MOCK&&MOCK.config||{},tg=window.Telegram&&window.Telegram.WebApp?window.Telegram.WebApp:null,TELEGRAM_LOGIN_WIDGET_URL="./telegram-widget.js",state={token:MOCK?"local-preview":localStorage.getItem("rw_webapp_token")||"",csrfToken:MOCK?"":readCookie("rw_webapp_csrf")||"",data:null,selectedPlan:null,referralParam:readReferralParam(),promoApplying:!1,payment:null,paymentFlowOpen:!1,promoModalOpen:!1,referralModalOpen:!1,accountMergeModalOpen:!1,accountMergeNotice:null,creatingPayment:!1,authInProgress:!1,authMode:CFG.emailAuthEnabled===!1?"telegram":"email",emailLoginPending:!1,emailLoginEmail:"",emailLoginCodeModalOpen:!1,emailLoginVerifying:!1,emailLoginResending:!1,emailLinkPending:!1,emailLinkEmail:"",promoStatusTimer:null,promoStatusType:"",promoStatusText:"",telegramLinkRendered:!1,telegramLinkInProgress:!1,toastTimer:null,userMenuOpen:!1,userMenuCloseTimer:null,langOverride:(function(){try{return localStorage.getItem("rw_webapp_lang")||""}catch(e){return""}})(),langMenuOpen:!1,langMenuCloseTimer:null},LANG_OPTIONS=[{code:"ru",label:"Русский",short:"RU",flag:"🇷🇺"},{code:"en",label:"English",short:"EN",flag:"🇬🇧"}],TW={iconBtn:"icon-btn",btnBase:"btn",btnIconOnly:"w-12 min-w-12 p-0",panelHead:"panel-head",flowCaption:"section-caption",sectionTitle:"section-title",metricLabel:"metric-label",metricValue:"metric-value",referralLinkRow:"referral-link-row",referralLinkValue:"referral-link-value",bonusRow:"bonus-row",empty:"empty-state",planCard:"plan-card",planCardActive:"plan-card-active",planName:"plan-name",planMeta:"plan-meta",planPrice:"plan-price",paymentMethodCard:"payment-method-card",paymentMethodCardPlatega:"payment-method-card--platega",paymentMethodCardCryptopay:"payment-method-card--cryptopay",notice:"notice",stepNum:"step-num",stepName:"step-name"},FALLBACK_I18N={ru:{page_title:"Моя подписка",loading:"Загрузка...",refresh:"Обновить",logout:"Выйти",subscription:"Подписка",subscription_subtitle:"Ваша ссылка подключения и срок доступа",ends_at:"Окончание",traffic:"Трафик",connect:"Подключиться",copy_link:"Скопировать ссылку",extend_subscription:"Купить подписку / Добавить дни",payment_title:"Оплата подписки",payment_caption:"Выберите период и нажмите способ оплаты. Сервис оплаты откроется сразу.",close:"Закрыть",close_payment:"Закрыть оплату",payment_steps:"Шаги оплаты",step_plan:"Срок",step_method:"Метод",step_result:"Платеж",select_period:"Выберите период",select_method:"Выберите способ оплаты",payment_status:"Статус платежа",payment_amount_label:"Стоимость выбранного периода",choose_payment_method:"Способ оплаты",back:"Назад",create_payment:"Создать платеж",open_payment:"Открыть оплату",check_payment:"Проверить оплату",choose_other_method:"Выбрать другой способ",pay_with_platega_button:"Оплатить картой (СБП)",pay_with_cryptopay_button:"Оплатить криптой",support:"Поддержка",account_title:"Аккаунт",account_caption:"Способы входа",email_label:"Email",telegram_label:"Telegram",linked:"Привязан",not_linked:"Не привязан",email_login_tab:"Email",telegram_login_tab:"Telegram",email_placeholder:"mail@example.com",code_placeholder:"000000",send_code:"Отправить код",resend_code:"Отправить еще раз",confirm:"Подтвердить",login:"Войти",login_title:"Войдите или зарегистрируйтесь",login_continue:"Продолжить",email_code_title:"Подтвердите вход",email_code_caption:"Введите 6-значный код из письма.",email_code_aria:"Код подтверждения",email_auth_disabled:"Вход по email пока не настроен.",email_required:"Введите email",email_invalid:"Введите корректный email",email_code_sending:"Отправляю код...",email_code_sent:"Код отправлен на почту",email_code_send_failed:"Не удалось отправить код",email_code_invalid:"Неверный код",email_code_expired:"Код устарел",email_rate_limited:"Повторная отправка доступна через {seconds} сек.",email_linked:"Email привязан",telegram_linked:"Telegram привязан",account_merge_toast:"Аккаунты объединены",account_merge_title:"Аккаунты объединены",account_merge_caption:"Оплаченные периоды сложились, и теперь у вас один аккаунт.",account_merge_body:"Мы объединили ваш Telegram и email в один профиль. Оплаченные периоды сложились, ссылка на подписку осталась прежней, а более поздний аккаунт был удалён автоматически.",account_merge_primary_account_label:"Оставлен аккаунт",account_merge_removed_account_label:"Удалён аккаунт",account_merge_end_date_label:"Новая дата окончания",account_merge_conflict:"Этот аккаунт уже связан с другими данными.",telegram_auth:"Telegram auth",telegram_auth_verifying:"Проверяю вход...",telegram_auth_failed:"Не удалось подтвердить Telegram-вход. Попробуйте еще раз.",telegram_auth_unavailable:"Telegram Login Widget недоступен. Проверьте username бота и доступ к telegram.org.",telegram_auth_access_denied:"Доступ запрещен.",active:"Активна",inactive:"Не активна",subscription_active:"Подписка активна",subscription_inactive:"Подписка не активна",sub_remaining_template:"Действует еще {value}",sub_ends_template:"Закончится {date}",sub_ended_template:"Закончилась {date}",sub_no_expiry:"Бессрочно",traffic_unlimited:"∞ бесконечный трафик",link_panel_title:"Привяжите способ входа",link_panel_caption:"Чтобы не потерять доступ к кабинету",link_email_title:"Привяжите email",link_email_caption:"Получайте код для входа на почту",link_telegram_title:"Привяжите Telegram",link_telegram_caption:"Входите одним нажатием через Telegram",guest_user:"Гость",no_active_subscription:"Нет активной подписки",not_available:"N/A",no_plans:"Тарифы не настроены.",access_period:"Срок доступа",choose_plan_first:"Сначала выберите срок подписки.",no_methods:"Нет доступных способов оплаты.",payment_not_created:"Платеж еще не создан.",invoice_sent:"Счет отправлен в чат с ботом. После оплаты вернитесь сюда и проверьте статус.",payment_created:"Платеж создан. Откройте оплату, завершите ее у провайдера и затем проверьте статус.",updating:"Обновляю данные",choose_plan_toast:"Выберите срок подписки",choose_method_toast:"Выберите способ оплаты",create_payment_toast:"Создайте платеж",payment_confirmed:"Оплата подтверждена",payment_pending:"Платеж пока не подтвержден",link_copied:"Ссылка скопирована",no_link:"Ссылка пока недоступна",payment_error:"Ошибка оплаты",privacy_policy:"Политика конфиденциальности",user_agreement:"Пользовательское соглашение",promo_title:"Промокод",promo_caption:"Введите код, чтобы начислить бонусные дни.",promo_placeholder:"PROMO2026",apply_promo:"Применить",promo_applied:"Промокод применен",promo_applied_ok:"Промокод применен, всё ок.",promo_empty:"Введите промокод",promo_invalid:"Промокод не найден или недействителен.",referral_title:"Пригласить друга",referral_caption:"Поделитесь ссылкой и получайте бонусы.",referral_code:"Ваш код",invited_friends:"Приглашено",purchased_friends:"Оплатили",copy_code:"Скопировать код",copy_webapp_link:"Скопировать ссылку Web App",copy_bot_link:"Скопировать ссылку бота",referral_copied:"Ссылка скопирована",code_copied:"Код скопирован",referral_site_label:"Пригласить через сайт",referral_telegram_label:"Пригласить через телеграм",referral_bonus_title:"Бонусы",referral_bonus_pair:"Вы: {inviter} дн. / друг: {friend} дн.",referral_no_bonuses:"Бонусы пока не настроены.",referral_bonus_explanation:"Бонусы начисляются один раз за каждого приглашенного пользователя, когда приглашенный пользователь покупает подписку."},en:{page_title:"My subscription",loading:"Loading...",refresh:"Refresh",logout:"Log out",subscription:"Subscription",subscription_subtitle:"Your connection link and access time",ends_at:"Ends at",traffic:"Traffic",connect:"Connect",copy_link:"Copy link",extend_subscription:"Renew subscription/Add days",payment_title:"Subscription payment",payment_caption:"Choose a period and tap a payment method. The payment service will open right away.",close:"Close",close_payment:"Close payment",payment_steps:"Payment steps",step_plan:"Period",step_method:"Method",step_result:"Payment",select_period:"Select period",select_method:"Select payment method",payment_status:"Payment status",payment_amount_label:"Selected period cost",choose_payment_method:"Payment method",back:"Back",create_payment:"Create payment",open_payment:"Open payment",check_payment:"Check payment",choose_other_method:"Choose another method",pay_with_platega_button:"Pay by card (SBP)",pay_with_cryptopay_button:"Pay with crypto",support:"Support",account_title:"Account",account_caption:"Sign-in methods",email_label:"Email",telegram_label:"Telegram",linked:"Linked",not_linked:"Not linked",email_login_tab:"Email",telegram_login_tab:"Telegram",email_placeholder:"mail@example.com",code_placeholder:"000000",send_code:"Send code",resend_code:"Send again",confirm:"Confirm",login:"Log in",login_title:"Log in or sign up",login_continue:"Continue",email_code_title:"Confirm login",email_code_caption:"Enter the 6-digit code from the email.",email_code_aria:"Verification code",email_auth_disabled:"Email sign-in is not configured yet.",email_required:"Enter your email",email_invalid:"Enter a valid email address",email_code_sending:"Sending code...",email_code_sent:"Code sent to email",email_code_send_failed:"Could not send code",email_code_invalid:"Invalid code",email_code_expired:"Code expired",email_rate_limited:"Try again in {seconds} sec.",email_linked:"Email linked",telegram_linked:"Telegram linked",account_merge_toast:"Accounts merged",account_merge_title:"Accounts merged",account_merge_caption:"Your paid periods were combined and you now have one account.",account_merge_body:"We merged your Telegram and email accounts into one profile. Your paid periods were combined, your subscription link stayed the same, and the later account was removed automatically.",account_merge_primary_account_label:"Kept account",account_merge_removed_account_label:"Removed account",account_merge_end_date_label:"New end date",account_merge_conflict:"This account is already linked to different data.",telegram_auth:"Telegram auth",telegram_auth_verifying:"Verifying login...",telegram_auth_failed:"Could not verify Telegram login. Try again.",telegram_auth_unavailable:"Telegram Login Widget is unavailable. Check the bot username and access to telegram.org.",telegram_auth_access_denied:"Access denied.",active:"Active",inactive:"Inactive",subscription_active:"Subscription is active",subscription_inactive:"Subscription is inactive",sub_remaining_template:"Valid for {value} more",sub_ends_template:"Expires {date}",sub_ended_template:"Expired {date}",sub_no_expiry:"No expiration",traffic_unlimited:"∞ unlimited traffic",link_panel_title:"Link a sign-in method",link_panel_caption:"So you never lose access",link_email_title:"Link email",link_email_caption:"Receive sign-in codes by email",link_telegram_title:"Link Telegram",link_telegram_caption:"Sign in with one tap via Telegram",guest_user:"Guest",no_active_subscription:"No active subscription",not_available:"N/A",no_plans:"Plans are not configured.",access_period:"Access period",choose_plan_first:"Choose a subscription period first.",no_methods:"No payment methods available.",payment_not_created:"Payment has not been created yet.",invoice_sent:"The invoice was sent to the bot chat. Pay it, then return here and check the status.",payment_created:"Payment was created. Open it, complete payment with the provider, then check the status.",updating:"Refreshing data",choose_plan_toast:"Choose a subscription period",choose_method_toast:"Choose a payment method",create_payment_toast:"Create a payment",payment_confirmed:"Payment confirmed",payment_pending:"Payment is not confirmed yet",link_copied:"Link copied",no_link:"Link is not available yet",payment_error:"Payment error",privacy_policy:"Privacy policy",user_agreement:"User agreement",promo_title:"Promo code",promo_caption:"Enter a code to add bonus days.",promo_placeholder:"PROMO2026",apply_promo:"Apply",promo_applied:"Promo code applied",promo_applied_ok:"Promo code applied, all good.",promo_empty:"Enter promo code",promo_invalid:"Promo code was not found or is not valid.",referral_title:"Invite friend",referral_caption:"Share your link and receive bonuses.",referral_code:"Your code",invited_friends:"Invited",purchased_friends:"Purchased",copy_code:"Copy code",copy_webapp_link:"Copy Web App link",copy_bot_link:"Copy bot link",referral_copied:"Link copied",code_copied:"Code copied",referral_site_label:"Invite via site",referral_telegram_label:"Invite via Telegram",referral_bonus_title:"Bonuses",referral_bonus_pair:"You: {inviter} d. / friend: {friend} d.",referral_no_bonuses:"Bonuses are not configured yet.",referral_bonus_explanation:"Bonuses are awarded once for each invited user when they purchase a subscription."}},I18N=readJsonScript("i18n")||MOCK&&MOCK.i18n||{},accent=CFG.primaryColor||"#00fe7a";if(document.documentElement.style.setProperty("--accent",accent),setBrand(),applyI18n(),applyLegalLinks(),CFG.supportUrl){const e=document.getElementById("support-link");e.href=CFG.supportUrl,e.classList.remove("hidden")}if(tg)try{tg.ready(),tg.expand(),typeof tg.isVersionAtLeast=="function"&&tg.isVersionAtLeast("6.1")&&(tg.setHeaderColor("#05070a"),tg.setBackgroundColor("#05070a"))}catch(e){}bindEmailLoginInput(),bindEmailCodeInput(),bindPromoInput(),document.addEventListener("keydown",e=>{handleDocumentKeyForUserMenu(e),e.key==="Escape"&&(state.emailLoginCodeModalOpen?closeEmailLoginCodeModal():state.accountMergeModalOpen?closeAccountMergeModal():state.promoModalOpen?closePromoModal():state.referralModalOpen?closeReferralModal():state.paymentFlowOpen&&closePaymentFlow())}),document.addEventListener("click",handleDocumentActionClick),document.addEventListener("click",handleDocumentClickForUserMenu),document.addEventListener("click",handleDocumentClickForLangMenu),renderLangMenu(),boot();async function boot(){if(showLoader(),MOCK){await loadData();return}const e=readTelegramLoginWidgetAuthData();if(e){if(await finalizeTelegramAuth(e))return;clearToken(),startExternalAuth({resetStatus:!1});return}if(tg&&tg.initData)try{if(await finalizeTelegramAuth(tg.initData,"init_data"))return}catch(n){}if(state.token||readCookie("rw_webapp_csrf"))try{await loadData();return}catch(n){clearToken()}await startExternalAuth()}function readTelegramLoginWidgetAuthData(){const e=new URLSearchParams(window.location.search),n=["id","first_name","last_name","username","photo_url","auth_date","hash"],a={};let i=!1;return n.forEach(o=>{e.has(o)&&(a[o]=e.get(o)||"",i=!0)}),!i||!a.id||!a.auth_date||!a.hash?null:a}function readReferralParam(){const e=new URLSearchParams(window.location.search),n=e.get("ref")||e.get("start")||e.get("start_param")||"",a=tg&&tg.initDataUnsafe&&tg.initDataUnsafe.start_param?tg.initDataUnsafe.start_param:"",i=String(a||n||"").trim();return i?(localStorage.setItem("rw_webapp_referral",i),i):localStorage.getItem("rw_webapp_referral")||""}function clearTelegramLoginWidgetQuery(){const e=new URL(window.location.href);["id","first_name","last_name","username","photo_url","auth_date","hash"].forEach(a=>e.searchParams.delete(a)),window.history&&window.history.replaceState&&window.history.replaceState({},document.title,e.pathname+e.search+e.hash)}async function finalizeTelegramAuth(e,n="auth_data"){if(state.authInProgress)return!1;state.authInProgress=!0,setAuthStatus(t("telegram_auth_verifying"));try{const a=n==="init_data"?{init_data:e}:{auth_data:e};state.referralParam&&(a.referral_code=state.referralParam);const i=await fetch(CFG.apiBase+"/auth/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)}),o=await i.json();if(i.ok&&o.ok&&o.token){setToken(o.token,o.csrf_token),clearTelegramLoginWidgetQuery();try{return setAuthStatus(""),await loadData(),!0}catch(l){return clearToken(),setAuthStatus(t("telegram_auth_failed"),!0),!1}}const r=o.error==="banned"||o.error==="access_denied"?"telegram_auth_access_denied":"telegram_auth_failed";setAuthStatus(t(r),!0)}catch(a){setAuthStatus(t("telegram_auth_failed"),!0)}finally{state.authInProgress=!1}return clearTelegramLoginWidgetQuery(),!1}async function handleTelegramLoginWidgetAuth(e){if(!e){setAuthStatus(t("telegram_auth_failed"),!0);return}await finalizeTelegramAuth(e,"auth_data")}function setAuthMode(e){state.authMode=e==="telegram"?"telegram":"email",renderAuthMode()}function renderAuthMode(){const e=document.getElementById("email-auth-tab"),n=document.getElementById("telegram-auth-tab"),a=document.getElementById("email-login-pane"),i=document.getElementById("telegram-login-pane"),o=document.querySelector(".login-auth-body"),r=CFG.emailAuthEnabled!==!1;!r&&state.authMode==="email"&&(state.authMode="telegram"),e.classList.toggle("active",state.authMode==="email"),n.classList.toggle("active",state.authMode==="telegram"),a.classList.toggle("hidden",state.authMode!=="email"),i.classList.toggle("hidden",state.authMode!=="telegram"),o&&o.classList.toggle("login-auth-body--telegram",state.authMode==="telegram"),e.disabled=!r,state.authMode==="telegram"?renderTelegramLoginWidget():r||setAuthStatus(t("email_auth_disabled"),!0)}function renderTelegramLoginWidget(){const e=document.getElementById("telegram-login-widget");if(!e)return;e.innerHTML="";const n=String(CFG.telegramLoginBotUsername||"").trim();if(!n){setAuthStatus(t("telegram_auth_unavailable"),!0);return}typeof window.onTelegramAuth!="function"&&(window.onTelegramAuth=async function(i){await handleTelegramLoginWidgetAuth(i)});const a=document.createElement("script");a.async=!0,a.src=TELEGRAM_LOGIN_WIDGET_URL,a.setAttribute("data-telegram-login",n),a.setAttribute("data-size","large"),a.setAttribute("data-userpic","true"),a.setAttribute("data-request-access","write"),a.setAttribute("data-onauth","onTelegramAuth(user)"),a.onerror=()=>setAuthStatus(t("telegram_auth_unavailable"),!0),e.appendChild(a)}function bindEmailLoginInput(){const e=document.getElementById("email-login-input");e&&(e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),requestEmailLoginCode())}),e.addEventListener("input",()=>{e.getAttribute("aria-invalid")==="true"&&(e.removeAttribute("aria-invalid"),setAuthStatus(""))}))}function bindEmailCodeInput(){const e=document.getElementById("email-login-code-input");e&&(e.addEventListener("input",()=>{e.value=sanitizeCode(e.value),updateEmailLoginCodeSlots()}),e.addEventListener("focus",updateEmailLoginCodeSlots),e.addEventListener("blur",updateEmailLoginCodeSlots),e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),sanitizeCode(e.value).length===6&&!state.emailLoginVerifying&&verifyEmailLoginCode())}))}function sanitizeCode(e){return String(e||"").replace(/\D/g,"").slice(0,6)}function isValidEmail(e){const n=normalizeEmail(e);return!!(n&&n.length<=254&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(n))}function readValidEmailLoginInput(){const e=document.getElementById("email-login-input"),n=normalizeEmail(e&&e.value);return!n||!isValidEmail(n)?(e&&(e.setAttribute("aria-invalid","true"),e.focus()),setAuthStatus(t(n?"email_invalid":"email_required"),!0),""):(e&&(e.value=n,e.removeAttribute("aria-invalid")),n)}function openEmailLoginCodeModal(e,n,a=!1){state.emailLoginEmail=e,state.emailLoginCodeModalOpen=!0;const i=document.getElementById("email-login-code-input");i&&(i.value=""),renderEmailLoginCodeModal(),setEmailCodeStatus(n||t("email_code_sent"),a),window.setTimeout(()=>{const o=document.getElementById("email-login-code-input");state.emailLoginCodeModalOpen&&o&&o.focus()},80)}function closeEmailLoginCodeModal(){state.emailLoginCodeModalOpen=!1;const e=document.getElementById("email-login-code-input");e&&(e.value=""),setEmailCodeStatus(""),renderEmailLoginCodeModal()}function renderEmailLoginCodeModal(){const e=document.getElementById("email-code-modal");if(!e)return;if(!state.emailLoginCodeModalOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.emailLoginCodeModalOpen||e.classList.add("hidden")},180);return}const n=document.getElementById("email-code-address");n&&(n.textContent=state.emailLoginEmail||"..."),e.classList.remove("hidden"),syncModalLock(),applyI18n(e),updateEmailLoginCodeSlots(),window.requestAnimationFrame(()=>e.classList.add("show"))}function updateEmailLoginCodeSlots(){const e=document.getElementById("email-login-code-input"),n=sanitizeCode(e&&e.value);e&&e.value!==n&&(e.value=n);for(let o=0;o<6;o+=1){const r=document.getElementById("email-code-slot-"+o);if(!r)continue;const l=n[o]||"";r.textContent=l,r.classList.toggle("filled",!!l),r.classList.toggle("active",state.emailLoginCodeModalOpen&&document.activeElement===e&&o===Math.min(n.length,5))}const a=document.getElementById("email-login-verify-btn");a&&(a.disabled=n.length!==6||state.emailLoginVerifying||state.emailLoginPending);const i=document.getElementById("email-login-resend-btn");i&&(i.disabled=state.emailLoginPending||state.emailLoginResending)}async function requestEmailLoginCode(){if(state.emailLoginPending)return;if(CFG.emailAuthEnabled===!1){setAuthStatus(t("email_auth_disabled"),!0);return}const e=readValidEmailLoginInput();if(e){state.emailLoginPending=!0,setButtonBusy("email-login-send-btn",!0),openEmailLoginCodeModal(e,t("email_code_sending")),setAuthStatus("");try{const n=await publicApi("/auth/email/request",{email:e,language:getLanguage()});if(!n.ok)throw n;setEmailCodeStatus(t("email_code_sent"))}catch(n){const a=emailErrorMessage(n,"email_code_send_failed");if(n&&n.error==="invalid_email"){closeEmailLoginCodeModal();const i=document.getElementById("email-login-input");i&&i.setAttribute("aria-invalid","true"),setAuthStatus(a,!0)}else setEmailCodeStatus(a,!0)}finally{state.emailLoginPending=!1,setButtonBusy("email-login-send-btn",!1),updateEmailLoginCodeSlots()}}}async function resendEmailLoginCode(){if(state.emailLoginPending||state.emailLoginResending)return;const e=state.emailLoginEmail||normalizeEmail(document.getElementById("email-login-input").value);if(!e||!isValidEmail(e)){setEmailCodeStatus(t(e?"email_invalid":"email_required"),!0);return}state.emailLoginResending=!0,updateEmailLoginCodeSlots(),setEmailCodeStatus(t("email_code_sending"));try{const n=await publicApi("/auth/email/request",{email:e,language:getLanguage()});if(!n.ok)throw n;state.emailLoginEmail=e;const a=document.getElementById("email-login-code-input");a&&(a.value=""),setEmailCodeStatus(t("email_code_sent"))}catch(n){setEmailCodeStatus(emailErrorMessage(n,"email_code_send_failed"),!0)}finally{state.emailLoginResending=!1,updateEmailLoginCodeSlots()}}async function verifyEmailLoginCode(){const e=state.emailLoginEmail||normalizeEmail(document.getElementById("email-login-input").value),n=document.getElementById("email-login-code-input"),a=sanitizeCode(n&&n.value);if(!e||a.length!==6){setEmailCodeStatus(t("email_code_invalid"),!0),updateEmailLoginCodeSlots();return}state.emailLoginVerifying=!0,updateEmailLoginCodeSlots(),setEmailCodeStatus(t("telegram_auth_verifying"));try{const i=await publicApi("/auth/email/verify",{email:e,code:a,referral_code:state.referralParam||""});if(!i.ok||!i.token)throw i;setToken(i.token,i.csrf_token),closeEmailLoginCodeModal(),await loadData()}catch(i){clearToken(),setEmailCodeStatus(emailErrorMessage(i,"email_code_invalid"),!0)}finally{state.emailLoginVerifying=!1,updateEmailLoginCodeSlots()}}function startExternalAuth(e={}){const n=e.resetStatus!==!1;showLogin(),n&&setAuthStatus(""),renderAuthMode()}async function loadData(){const e=await api("/me");if(!e.ok)throw new Error(e.error||"load failed");state.data=e,setBrand(),applyI18n(),applyLegalLinks();const n=e.plans||[];state.selectedPlan=getDefaultPaymentPlan(n),state.payment=null,state.creatingPayment=!1,state.telegramLinkRendered=!1,render(),showApp()}function render(){renderSubscription(state.data.subscription),renderUserMenu(state.data.user||{}),renderAccount(state.data.user||{}),renderReferral(state.data.referral||{}),renderAccountMergeModal(),renderPaymentFlow()}function renderSubscription(e){const n=document.getElementById("sub-status"),a=document.getElementById("sub-status-text"),i=document.getElementById("remaining"),o=document.getElementById("end-date-caption");if(n.classList.toggle("active",!!e.active),a.textContent=e.active?t("subscription_active"):t("subscription_inactive"),e.active){const r=formatCompactRemaining(e.days_left,getLanguage())||e.remaining_text||"";i.textContent=r}else i.textContent=t("no_active_subscription");if(e.end_date_text){const r=e.active?"sub_ends_template":"sub_ended_template";o.textContent=t(r,{date:e.end_date_text}),o.classList.remove("hidden")}else e.active?(o.textContent=t("sub_no_expiry"),o.classList.remove("hidden")):(o.textContent="",o.classList.add("hidden"));renderTraffic(e),document.getElementById("connect-actions").classList.toggle("hidden",!e.connect_url&&!e.config_link)}function renderTraffic(e){const n=document.getElementById("traffic-bar"),a=document.getElementById("traffic-bar-fill"),i=document.getElementById("traffic");if(!n||!a||!i)return;const o=Number(e.traffic_limit_bytes),r=Number(e.traffic_used_bytes),l=Number.isFinite(o)&&o>0,s=Number.isFinite(r)&&r>=0,d=e.traffic_used||t("not_available"),c=e.traffic_limit||t("not_available");if(n.classList.remove("over","unlimited"),!l){n.classList.add("unlimited"),a.style.width="100%",i.textContent=t("traffic_unlimited"),n.setAttribute("aria-valuenow","0");return}const u=s?r/o:0,m=Math.max(0,Math.min(100,Math.round(u*100)));a.style.width=m+"%",i.textContent=d+" / "+c,n.setAttribute("aria-valuenow",String(m)),s&&r>=o&&n.classList.add("over")}function formatCompactRemaining(e,n){const a=Math.max(0,Math.floor(Number(e)||0));if(a<=0)return null;if(a>=365){const i=Math.round(a/365*2)/2;return formatDecimal(i,n)+" "+pluralizeUnit(i,"year",n)}if(a>=30){const i=Math.round(a/30*2)/2;return formatDecimal(i,n)+" "+pluralizeUnit(i,"month",n)}return formatDecimal(a,n)+" "+pluralizeUnit(a,"day",n)}function formatDecimal(e,n){if(Number.isInteger(e))return String(e);const a=e.toFixed(1);return n==="ru"?a.replace(".",","):a}function pluralizeUnit(e,n,a){const i={year:["year","years"],month:["month","months"],day:["day","days"]},o={year:["год","года","лет"],month:["месяц","месяца","месяцев"],day:["день","дня","дней"]};if(a==="en")return e===1?i[n][0]:i[n][1];const r=o[n];if(!Number.isInteger(e))return r[1];const l=Math.abs(e),s=l%10,d=l%100;return s===1&&d!==11?r[0]:s>=2&&s<=4&&(d<12||d>14)?r[1]:r[2]}function renderUserMenu(e){const n=!!(e.email&&e.email_verified),a=!!e.telegram_linked,i=getUserDisplayName(e),o=getUserSecondaryText(e,n,a),r=getUserAvatarSrc(e),l=buildIdenticon(getUserAvatarSeed(e,n)),s=document.getElementById("user-chip-name"),d=document.getElementById("user-chip-avatar"),c=document.getElementById("user-dropdown-avatar"),u=document.getElementById("user-dropdown-name"),m=document.getElementById("user-dropdown-sub"),p=document.getElementById("user-dropdown-email-status"),f=document.getElementById("user-dropdown-telegram-status");s.textContent=i,setAvatarWithFallback(d,r,l,i),setAvatarWithFallback(c,r,l,i),u.textContent=i,m.textContent=o,m.classList.toggle("hidden",!o),p.textContent=n?e.email:t("not_linked"),p.classList.toggle("linked",n),p.classList.toggle("unlinked",!n),f.textContent=a?e.telegram_id?String(e.telegram_id):t("linked"):t("not_linked"),f.classList.toggle("linked",a),f.classList.toggle("unlinked",!a)}function renderAccount(e){const n=!!(e.email&&e.email_verified),a=!!e.telegram_linked,i=!!(state.data.settings&&state.data.settings.email_auth_enabled),o=!n&&i,r=!a,l=document.getElementById("email-link-box"),s=document.getElementById("telegram-link-box"),d=document.getElementById("link-panel");l.classList.toggle("hidden",!o),s.classList.toggle("hidden",!r),d.classList.toggle("hidden",!o&&!r),a?state.telegramLinkRendered=!1:renderTelegramLinkWidget()}function getTelegramInitUser(){return!tg||!tg.initDataUnsafe||!tg.initDataUnsafe.user?null:tg.initDataUnsafe.user}function getUserAvatarSeed(e,n){const a=getTelegramInitUser();return n&&e&&e.email?e.email.trim().toLowerCase():String(e&&e.id||e&&e.telegram_id||a&&a.id||"guest")}function getUserAvatarSrc(e){const n=getTelegramInitUser();return n&&n.photo_url?n.photo_url:e&&e.telegram_photo_url?e.telegram_photo_url:""}function getUserDisplayName(e){const n=getTelegramInitUser();return n&&n.first_name?n.first_name:e&&e.first_name?e.first_name:e&&e.username?"@"+e.username:n&&n.username?"@"+n.username:e&&e.email?e.email:t("guest_user")}function getUserSecondaryText(e,n,a){return n&&e.email?e.email:a&&e.telegram_id?"#"+e.telegram_id:""}function setAvatarWithFallback(e,n,a,i){if(e){if(e.alt=i||"",!n){e.onerror=null,e.src=a;return}e.onerror=()=>{e.onerror=null,e.src=a},e.src=n}}function buildIdenticon(e){const n=String(e||"guest");let a=2166136261;for(let c=0;c>>0;const i=a%360,o="hsl("+i+", 60%, 18%)",r="hsl("+(i+180)%360+", 78%, 62%)";let l="",s=a;for(let c=0;c<5;c++)for(let u=0;u<3;u++){const m=s&1;s=s>>>1^(s&1?46080:0),m&&(l+='',u!==2&&(l+=''))}const d=''+l+"";return"data:image/svg+xml;utf8,"+encodeURIComponent(d)}function toggleUserMenu(e){const n=typeof e=="boolean"?e:!state.userMenuOpen;state.userMenuOpen=n;const a=document.getElementById("user-chip"),i=document.getElementById("user-dropdown");if(!(!a||!i)){if(state.userMenuCloseTimer&&(window.clearTimeout(state.userMenuCloseTimer),state.userMenuCloseTimer=null),a.setAttribute("aria-expanded",n?"true":"false"),n){i.classList.remove("hidden"),i.classList.remove("show"),window.requestAnimationFrame(()=>{state.userMenuOpen&&i.classList.add("show")});return}i.classList.remove("show"),state.userMenuCloseTimer=window.setTimeout(()=>{state.userMenuOpen||i.classList.add("hidden"),state.userMenuCloseTimer=null},180)}}function handleDocumentClickForUserMenu(e){if(!state.userMenuOpen)return;const n=document.querySelector(".user-menu");n&&n.contains(e.target)||toggleUserMenu(!1)}function handleDocumentKeyForUserMenu(e){e.key==="Escape"&&state.userMenuOpen&&toggleUserMenu(!1),e.key==="Escape"&&state.langMenuOpen&&toggleLangMenu(!1)}function renderLangMenu(){const e=getLanguage(),n=document.getElementById("lang-chip-label"),a=document.getElementById("lang-chip-flag"),i=LANG_OPTIONS.find(r=>r.code===e)||LANG_OPTIONS[0];n&&(n.textContent=i.short),a&&(a.textContent=i.flag);const o=document.getElementById("lang-dropdown");o&&(o.innerHTML=LANG_OPTIONS.map(r=>'").join(""),o.querySelectorAll("[data-lang]").forEach(r=>{r.addEventListener("click",()=>setLanguage(r.dataset.lang))}))}function toggleLangMenu(e){const n=typeof e=="boolean"?e:!state.langMenuOpen;state.langMenuOpen=n;const a=document.getElementById("lang-chip"),i=document.getElementById("lang-dropdown");if(!(!a||!i)){if(state.langMenuCloseTimer&&(window.clearTimeout(state.langMenuCloseTimer),state.langMenuCloseTimer=null),a.setAttribute("aria-expanded",n?"true":"false"),n){i.classList.remove("hidden"),i.classList.remove("show"),window.requestAnimationFrame(()=>{state.langMenuOpen&&i.classList.add("show")});return}i.classList.remove("show"),state.langMenuCloseTimer=window.setTimeout(()=>{state.langMenuOpen||i.classList.add("hidden"),state.langMenuCloseTimer=null},180)}}function handleDocumentClickForLangMenu(e){if(!state.langMenuOpen)return;const n=document.querySelector(".lang-menu");n&&n.contains(e.target)||toggleLangMenu(!1)}function handleDocumentActionClick(e){const n=e.target instanceof Element?e.target.closest("[data-action]"):null;if(!n||!document.contains(n)||n.disabled)return;const a=String(n.dataset.action||"");if(a){switch(a){case"toggle-user-menu":toggleUserMenu();break;case"toggle-lang-menu":toggleLangMenu();break;case"logout":logout();break;case"open-connect-link":openConnectLink();break;case"toggle-payment-flow":togglePaymentFlow();break;case"open-referral-modal":openReferralModal();break;case"open-promo-modal":openPromoModal();break;case"close-payment-flow":closePaymentFlow();break;case"close-promo-modal":closePromoModal();break;case"close-referral-modal":closeReferralModal();break;case"close-account-merge-modal":closeAccountMergeModal();break;case"close-email-login-code-modal":closeEmailLoginCodeModal();break;case"request-email-link-code":requestEmailLinkCode();break;case"verify-email-link-code":verifyEmailLinkCode();break;case"request-email-login-code":requestEmailLoginCode();break;case"verify-email-login-code":verifyEmailLoginCode();break;case"resend-email-login-code":resendEmailLoginCode();break;case"set-auth-mode":setAuthMode(n.dataset.mode||"email");break;case"apply-promo-code":applyPromoCode();break;case"select-plan":selectPlan(n.dataset.months||"");break;case"create-payment":createAndOpenPayment(n.dataset.method||"");break;case"copy-referral-link":copyReferralLink(n.dataset.kind||"webapp");break;case"copy-config-link":copyConfigLink();break;case"check-payment":checkPayment();break;default:return}e.preventDefault()}}function openPromoModal(){state.promoModalOpen=!0,clearPromoStatus(),renderPromoModal(),window.setTimeout(()=>{const e=document.getElementById("promo-code-input");e&&e.focus()},80)}function closePromoModal(){state.promoModalOpen=!1,clearPromoStatus(),renderPromoModal()}function renderPromoModal(){const e=document.getElementById("promo-modal");if(e){if(!state.promoModalOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.promoModalOpen||e.classList.add("hidden")},180);return}e.classList.remove("hidden"),syncModalLock(),renderPromoStatus(),applyI18n(e),window.requestAnimationFrame(()=>e.classList.add("show"))}}function bindPromoInput(){const e=document.getElementById("promo-code-input");e&&(e.addEventListener("input",()=>{e.getAttribute("aria-invalid")==="true"&&e.removeAttribute("aria-invalid"),state.promoStatusText&&clearPromoStatus()}),e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),applyPromoCode())}))}function setPromoStatus(e,n="error"){state.promoStatusType=n==="success"?"success":"error",state.promoStatusText=normalizeStatusText(e||""),renderPromoStatus()}function clearPromoStatus(){state.promoStatusType="",state.promoStatusText="",renderPromoStatus()}function renderPromoStatus(){const e=document.getElementById("promo-status");if(e){if(!state.promoStatusText){e.textContent="",e.classList.add("hidden"),e.classList.remove("error","success");return}e.textContent=state.promoStatusText,e.classList.remove("hidden"),e.classList.toggle("error",state.promoStatusType==="error"),e.classList.toggle("success",state.promoStatusType==="success")}}function normalizeStatusText(e){const n=String(e||""),a=document.createElement("div");return a.innerHTML=n,(a.textContent||a.innerText||"").trim()}function openReferralModal(){state.referralModalOpen=!0,renderReferral(state.data&&state.data.referral||{})}function closeReferralModal(){state.referralModalOpen=!1,renderReferral(state.data&&state.data.referral||{})}function closeToolModals(){state.promoModalOpen=!1,state.referralModalOpen=!1,state.accountMergeModalOpen=!1,clearPromoStatus(),renderPromoModal(),renderReferral(state.data&&state.data.referral||{}),renderAccountMergeModal()}function openAccountMergeModal(e=null){e&&(state.accountMergeNotice=e),state.accountMergeModalOpen=!0,renderAccountMergeModal()}function closeAccountMergeModal(){state.accountMergeModalOpen=!1,renderAccountMergeModal()}function renderAccountMergeModal(){const e=document.getElementById("account-merge-modal"),n=document.getElementById("account-merge-panel");if(!e||!n)return;if(!state.accountMergeModalOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.accountMergeModalOpen||e.classList.add("hidden")},180);return}e.classList.remove("hidden"),syncModalLock();const a=state.accountMergeNotice||{},i=a.primary_user_id!=null?String(a.primary_user_id):t("not_available"),o=a.removed_user_id!=null?String(a.removed_user_id):t("not_available"),r=a.final_end_date_text||t("not_available");n.innerHTML=` +const MOCK=(()=>{const e=window.__WEBAPP_DEV_MOCK__,n=window.location.hostname,a=window.location.protocol==="file:"||n==="localhost"||n==="127.0.0.1"||n==="";return e&&a?e:null})();function readJsonScript(e){const n=document.getElementById(e);if(!n||!n.textContent)return null;try{return JSON.parse(n.textContent)}catch(a){return console.warn("Failed to parse JSON config from #"+e,a),null}}const CFG=readJsonScript("webapp-config")||MOCK&&MOCK.config||{},tg=window.Telegram&&window.Telegram.WebApp?window.Telegram.WebApp:null,TELEGRAM_LOGIN_WIDGET_URL="./telegram-widget.js",state={token:MOCK?"local-preview":localStorage.getItem("rw_webapp_token")||"",csrfToken:MOCK?"":readCookie("rw_webapp_csrf")||"",data:null,selectedPlan:null,referralParam:readReferralParam(),promoApplying:!1,payment:null,paymentFlowOpen:!1,promoModalOpen:!1,referralModalOpen:!1,accountMergeModalOpen:!1,accountMergeNotice:null,creatingPayment:!1,authInProgress:!1,authMode:CFG.emailAuthEnabled===!1?"telegram":"email",emailLoginPending:!1,emailLoginEmail:"",emailLoginCodeModalOpen:!1,emailLoginVerifying:!1,emailLoginResending:!1,emailLinkPending:!1,emailLinkEmail:"",promoStatusTimer:null,promoStatusType:"",promoStatusText:"",telegramLinkRendered:!1,telegramLinkInProgress:!1,toastTimer:null,userMenuOpen:!1,userMenuCloseTimer:null,langOverride:(function(){try{return localStorage.getItem("rw_webapp_lang")||""}catch(e){return""}})(),langMenuOpen:!1,langMenuCloseTimer:null},LANG_OPTIONS=[{code:"ru",label:"Русский",short:"RU",flag:"🇷🇺"},{code:"en",label:"English",short:"EN",flag:"🇬🇧"}],TW={iconBtn:"icon-btn",btnBase:"btn",btnIconOnly:"w-12 min-w-12 p-0",panelHead:"panel-head",flowCaption:"section-caption",sectionTitle:"section-title",metricLabel:"metric-label",metricValue:"metric-value",referralLinkRow:"referral-link-row",referralLinkValue:"referral-link-value",bonusRow:"bonus-row",empty:"empty-state",planCard:"plan-card",planCardActive:"plan-card-active",planName:"plan-name",planMeta:"plan-meta",planPrice:"plan-price",paymentMethodCard:"payment-method-card",paymentMethodCardPlatega:"payment-method-card--platega",paymentMethodCardCryptopay:"payment-method-card--cryptopay",notice:"notice",stepNum:"step-num",stepName:"step-name"},FALLBACK_I18N={ru:{page_title:"Моя подписка",loading:"Загрузка...",refresh:"Обновить",logout:"Выйти",subscription:"Подписка",subscription_subtitle:"Ваша ссылка подключения и срок доступа",ends_at:"Окончание",traffic:"Трафик",connect:"Подключиться",copy_link:"Скопировать ссылку",extend_subscription:"Купить подписку / Добавить дни",payment_title:"Оплата подписки",payment_caption:"Выберите период и нажмите способ оплаты. Сервис оплаты откроется сразу.",close:"Закрыть",close_payment:"Закрыть оплату",payment_steps:"Шаги оплаты",step_plan:"Срок",step_method:"Метод",step_result:"Платеж",select_period:"Выберите период",select_method:"Выберите способ оплаты",payment_status:"Статус платежа",payment_amount_label:"Стоимость выбранного периода",choose_payment_method:"Способ оплаты",back:"Назад",create_payment:"Создать платеж",open_payment:"Открыть оплату",check_payment:"Проверить оплату",choose_other_method:"Выбрать другой способ",pay_with_platega_button:"Оплатить картой (СБП)",pay_with_cryptopay_button:"Оплатить криптой",support:"Поддержка",account_title:"Аккаунт",account_caption:"Способы входа",email_label:"Email",telegram_label:"Telegram",linked:"Привязан",not_linked:"Не привязан",email_login_tab:"Email",telegram_login_tab:"Telegram",email_placeholder:"mail@example.com",code_placeholder:"000000",send_code:"Отправить код",resend_code:"Отправить еще раз",confirm:"Подтвердить",login:"Войти",login_title:"Войдите или зарегистрируйтесь",login_continue:"Продолжить",email_code_title:"Подтвердите вход",email_code_caption:"Введите 6-значный код из письма.",email_code_aria:"Код подтверждения",email_auth_disabled:"Вход по email пока не настроен.",email_required:"Введите email",email_invalid:"Введите корректный email",email_code_sending:"Отправляю код...",email_code_sent:"Код отправлен на почту",email_code_send_failed:"Не удалось отправить код",email_code_invalid:"Неверный код",email_code_expired:"Код устарел",email_rate_limited:"Повторная отправка доступна через {seconds} сек.",email_linked:"Email привязан",telegram_linked:"Telegram привязан",account_merge_toast:"Аккаунты объединены",account_merge_title:"Аккаунты объединены",account_merge_caption:"Оплаченные периоды сложились, и теперь у вас один аккаунт.",account_merge_body:"Мы объединили ваш Telegram и email в один профиль. Оплаченные периоды сложились, ссылка на подписку осталась прежней, а более поздний аккаунт был удалён автоматически.",account_merge_primary_account_label:"Оставлен аккаунт",account_merge_removed_account_label:"Удалён аккаунт",account_merge_end_date_label:"Новая дата окончания",account_merge_conflict:"Этот аккаунт уже связан с другими данными.",telegram_auth:"Telegram auth",telegram_auth_verifying:"Проверяю вход...",telegram_auth_failed:"Не удалось подтвердить Telegram-вход. Попробуйте еще раз.",telegram_auth_unavailable:"Telegram Login Widget недоступен. Проверьте username бота и доступ к telegram.org.",telegram_auth_access_denied:"Доступ запрещен.",active:"Активна",inactive:"Не активна",subscription_active:"Подписка активна",subscription_inactive:"Подписка не активна",sub_remaining_template:"Действует еще {value}",sub_ends_template:"Закончится {date}",sub_ended_template:"Закончилась {date}",sub_no_expiry:"Бессрочно",traffic_unlimited:"∞ бесконечный трафик",link_panel_title:"Привяжите способ входа",link_panel_caption:"Чтобы не потерять доступ к кабинету",link_email_title:"Привяжите email",link_email_caption:"Получайте код для входа на почту",link_telegram_title:"Привяжите Telegram",link_telegram_caption:"Входите одним нажатием через Telegram",guest_user:"Гость",no_active_subscription:"Нет активной подписки",not_available:"N/A",no_plans:"Тарифы не настроены.",access_period:"Срок доступа",choose_plan_first:"Сначала выберите срок подписки.",no_methods:"Нет доступных способов оплаты.",payment_not_created:"Платеж еще не создан.",invoice_sent:"Счет отправлен в чат с ботом. После оплаты вернитесь сюда и проверьте статус.",payment_created:"Платеж создан. Откройте оплату, завершите ее у провайдера и затем проверьте статус.",updating:"Обновляю данные",choose_plan_toast:"Выберите срок подписки",choose_method_toast:"Выберите способ оплаты",create_payment_toast:"Создайте платеж",payment_confirmed:"Оплата подтверждена",payment_pending:"Платеж пока не подтвержден",link_copied:"Ссылка скопирована",no_link:"Ссылка пока недоступна",payment_error:"Ошибка оплаты",privacy_policy:"Политика конфиденциальности",user_agreement:"Пользовательское соглашение",promo_title:"Промокод",promo_caption:"Введите код, чтобы начислить бонусные дни.",promo_placeholder:"PROMO2026",apply_promo:"Применить",promo_applied:"Промокод применен",promo_applied_ok:"Промокод применен, всё ок.",promo_empty:"Введите промокод",promo_invalid:"Промокод не найден или недействителен.",referral_title:"Пригласить друга",referral_caption:"Поделитесь ссылкой и получайте бонусы.",referral_code:"Ваш код",invited_friends:"Приглашено",purchased_friends:"Оплатили",copy_code:"Скопировать код",copy_webapp_link:"Скопировать ссылку Web App",copy_bot_link:"Скопировать ссылку бота",referral_copied:"Ссылка скопирована",code_copied:"Код скопирован",referral_site_label:"Пригласить через сайт",referral_telegram_label:"Пригласить через телеграм",referral_bonus_title:"Бонусы",referral_bonus_pair:"Вы: {inviter} дн. / друг: {friend} дн.",referral_no_bonuses:"Бонусы пока не настроены.",referral_bonus_explanation:"Бонусы начисляются один раз за каждого приглашенного пользователя, когда приглашенный пользователь покупает подписку."},en:{page_title:"My subscription",loading:"Loading...",refresh:"Refresh",logout:"Log out",subscription:"Subscription",subscription_subtitle:"Your connection link and access time",ends_at:"Ends at",traffic:"Traffic",connect:"Connect",copy_link:"Copy link",extend_subscription:"Renew subscription/Add days",payment_title:"Subscription payment",payment_caption:"Choose a period and tap a payment method. The payment service will open right away.",close:"Close",close_payment:"Close payment",payment_steps:"Payment steps",step_plan:"Period",step_method:"Method",step_result:"Payment",select_period:"Select period",select_method:"Select payment method",payment_status:"Payment status",payment_amount_label:"Selected period cost",choose_payment_method:"Payment method",back:"Back",create_payment:"Create payment",open_payment:"Open payment",check_payment:"Check payment",choose_other_method:"Choose another method",pay_with_platega_button:"Pay by card (SBP)",pay_with_cryptopay_button:"Pay with crypto",support:"Support",account_title:"Account",account_caption:"Sign-in methods",email_label:"Email",telegram_label:"Telegram",linked:"Linked",not_linked:"Not linked",email_login_tab:"Email",telegram_login_tab:"Telegram",email_placeholder:"mail@example.com",code_placeholder:"000000",send_code:"Send code",resend_code:"Send again",confirm:"Confirm",login:"Log in",login_title:"Log in or sign up",login_continue:"Continue",email_code_title:"Confirm login",email_code_caption:"Enter the 6-digit code from the email.",email_code_aria:"Verification code",email_auth_disabled:"Email sign-in is not configured yet.",email_required:"Enter your email",email_invalid:"Enter a valid email address",email_code_sending:"Sending code...",email_code_sent:"Code sent to email",email_code_send_failed:"Could not send code",email_code_invalid:"Invalid code",email_code_expired:"Code expired",email_rate_limited:"Try again in {seconds} sec.",email_linked:"Email linked",telegram_linked:"Telegram linked",account_merge_toast:"Accounts merged",account_merge_title:"Accounts merged",account_merge_caption:"Your paid periods were combined and you now have one account.",account_merge_body:"We merged your Telegram and email accounts into one profile. Your paid periods were combined, your subscription link stayed the same, and the later account was removed automatically.",account_merge_primary_account_label:"Kept account",account_merge_removed_account_label:"Removed account",account_merge_end_date_label:"New end date",account_merge_conflict:"This account is already linked to different data.",telegram_auth:"Telegram auth",telegram_auth_verifying:"Verifying login...",telegram_auth_failed:"Could not verify Telegram login. Try again.",telegram_auth_unavailable:"Telegram Login Widget is unavailable. Check the bot username and access to telegram.org.",telegram_auth_access_denied:"Access denied.",active:"Active",inactive:"Inactive",subscription_active:"Subscription is active",subscription_inactive:"Subscription is inactive",sub_remaining_template:"Valid for {value} more",sub_ends_template:"Expires {date}",sub_ended_template:"Expired {date}",sub_no_expiry:"No expiration",traffic_unlimited:"∞ unlimited traffic",link_panel_title:"Link a sign-in method",link_panel_caption:"So you never lose access",link_email_title:"Link email",link_email_caption:"Receive sign-in codes by email",link_telegram_title:"Link Telegram",link_telegram_caption:"Sign in with one tap via Telegram",guest_user:"Guest",no_active_subscription:"No active subscription",not_available:"N/A",no_plans:"Plans are not configured.",access_period:"Access period",choose_plan_first:"Choose a subscription period first.",no_methods:"No payment methods available.",payment_not_created:"Payment has not been created yet.",invoice_sent:"The invoice was sent to the bot chat. Pay it, then return here and check the status.",payment_created:"Payment was created. Open it, complete payment with the provider, then check the status.",updating:"Refreshing data",choose_plan_toast:"Choose a subscription period",choose_method_toast:"Choose a payment method",create_payment_toast:"Create a payment",payment_confirmed:"Payment confirmed",payment_pending:"Payment is not confirmed yet",link_copied:"Link copied",no_link:"Link is not available yet",payment_error:"Payment error",privacy_policy:"Privacy policy",user_agreement:"User agreement",promo_title:"Promo code",promo_caption:"Enter a code to add bonus days.",promo_placeholder:"PROMO2026",apply_promo:"Apply",promo_applied:"Promo code applied",promo_applied_ok:"Promo code applied, all good.",promo_empty:"Enter promo code",promo_invalid:"Promo code was not found or is not valid.",referral_title:"Invite friend",referral_caption:"Share your link and receive bonuses.",referral_code:"Your code",invited_friends:"Invited",purchased_friends:"Purchased",copy_code:"Copy code",copy_webapp_link:"Copy Web App link",copy_bot_link:"Copy bot link",referral_copied:"Link copied",code_copied:"Code copied",referral_site_label:"Invite via site",referral_telegram_label:"Invite via Telegram",referral_bonus_title:"Bonuses",referral_bonus_pair:"You: {inviter} d. / friend: {friend} d.",referral_no_bonuses:"Bonuses are not configured yet.",referral_bonus_explanation:"Bonuses are awarded once for each invited user when they purchase a subscription."}},I18N=function(){var s=readJsonScript("i18n")||MOCK&&MOCK.i18n||{},m={},L=new Set(Object.keys(FALLBACK_I18N).concat(Object.keys(s)));L.forEach(function(l){m[l]=Object.assign({},FALLBACK_I18N[l]||{},s[l]||{})});return m}(),accent=CFG.primaryColor||"#00fe7a";if(document.documentElement.style.setProperty("--accent",accent),setBrand(),applyI18n(),applyLegalLinks(),CFG.supportUrl){const e=document.getElementById("support-link");e.href=CFG.supportUrl,e.classList.remove("hidden")}if(tg)try{tg.ready(),tg.expand(),typeof tg.isVersionAtLeast=="function"&&tg.isVersionAtLeast("6.1")&&(tg.setHeaderColor("#05070a"),tg.setBackgroundColor("#05070a"))}catch(e){}bindEmailLoginInput(),bindEmailCodeInput(),bindPromoInput(),document.addEventListener("keydown",e=>{handleDocumentKeyForUserMenu(e),e.key==="Escape"&&(state.emailLoginCodeModalOpen?closeEmailLoginCodeModal():state.accountMergeModalOpen?closeAccountMergeModal():state.promoModalOpen?closePromoModal():state.referralModalOpen?closeReferralModal():state.paymentFlowOpen&&closePaymentFlow())}),document.addEventListener("click",handleDocumentActionClick),document.addEventListener("click",handleDocumentClickForUserMenu),document.addEventListener("click",handleDocumentClickForLangMenu),renderLangMenu(),boot();async function boot(){if(showLoader(),MOCK){await loadData();return}const e=readTelegramLoginWidgetAuthData();if(e){if(await finalizeTelegramAuth(e))return;clearToken(),startExternalAuth({resetStatus:!1});return}if(tg&&tg.initData)try{if(await finalizeTelegramAuth(tg.initData,"init_data"))return}catch(n){}if(state.token||readCookie("rw_webapp_csrf"))try{await loadData();return}catch(n){clearToken()}await startExternalAuth()}function readTelegramLoginWidgetAuthData(){const e=new URLSearchParams(window.location.search),n=["id","first_name","last_name","username","photo_url","auth_date","hash"],a={};let i=!1;return n.forEach(o=>{e.has(o)&&(a[o]=e.get(o)||"",i=!0)}),!i||!a.id||!a.auth_date||!a.hash?null:a}function readReferralParam(){const e=new URLSearchParams(window.location.search),n=e.get("ref")||e.get("start")||e.get("start_param")||"",a=tg&&tg.initDataUnsafe&&tg.initDataUnsafe.start_param?tg.initDataUnsafe.start_param:"",i=String(a||n||"").trim();return i?(localStorage.setItem("rw_webapp_referral",i),i):localStorage.getItem("rw_webapp_referral")||""}function clearTelegramLoginWidgetQuery(){const e=new URL(window.location.href);["id","first_name","last_name","username","photo_url","auth_date","hash"].forEach(a=>e.searchParams.delete(a)),window.history&&window.history.replaceState&&window.history.replaceState({},document.title,e.pathname+e.search+e.hash)}async function finalizeTelegramAuth(e,n="auth_data"){if(state.authInProgress)return!1;state.authInProgress=!0,setAuthStatus(t("telegram_auth_verifying"));try{const a=n==="init_data"?{init_data:e}:{auth_data:e};state.referralParam&&(a.referral_code=state.referralParam);const i=await fetch(CFG.apiBase+"/auth/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)}),o=await i.json();if(i.ok&&o.ok&&o.token){setToken(o.token,o.csrf_token),clearTelegramLoginWidgetQuery();try{return setAuthStatus(""),await loadData(),!0}catch(l){return clearToken(),setAuthStatus(t("telegram_auth_failed"),!0),!1}}const r=o.error==="banned"||o.error==="access_denied"?"telegram_auth_access_denied":"telegram_auth_failed";setAuthStatus(t(r),!0)}catch(a){setAuthStatus(t("telegram_auth_failed"),!0)}finally{state.authInProgress=!1}return clearTelegramLoginWidgetQuery(),!1}async function handleTelegramLoginWidgetAuth(e){if(!e){setAuthStatus(t("telegram_auth_failed"),!0);return}await finalizeTelegramAuth(e,"auth_data")}function setAuthMode(e){state.authMode=e==="telegram"?"telegram":"email",renderAuthMode()}function renderAuthMode(){const e=document.getElementById("email-auth-tab"),n=document.getElementById("telegram-auth-tab"),a=document.getElementById("email-login-pane"),i=document.getElementById("telegram-login-pane"),o=document.querySelector(".login-auth-body"),r=CFG.emailAuthEnabled!==!1;!r&&state.authMode==="email"&&(state.authMode="telegram"),e.classList.toggle("active",state.authMode==="email"),n.classList.toggle("active",state.authMode==="telegram"),a.classList.toggle("hidden",state.authMode!=="email"),i.classList.toggle("hidden",state.authMode!=="telegram"),o&&o.classList.toggle("login-auth-body--telegram",state.authMode==="telegram"),e.disabled=!r,state.authMode==="telegram"?renderTelegramLoginWidget():r||setAuthStatus(t("email_auth_disabled"),!0)}function renderTelegramLoginWidget(){const e=document.getElementById("telegram-login-widget");if(!e)return;e.innerHTML="";const n=String(CFG.telegramLoginBotUsername||"").trim();if(!n){setAuthStatus(t("telegram_auth_unavailable"),!0);return}typeof window.onTelegramAuth!="function"&&(window.onTelegramAuth=async function(i){await handleTelegramLoginWidgetAuth(i)});const a=document.createElement("script");a.async=!0,a.src=TELEGRAM_LOGIN_WIDGET_URL,a.setAttribute("data-telegram-login",n),a.setAttribute("data-size","large"),a.setAttribute("data-userpic","true"),a.setAttribute("data-request-access","write"),a.setAttribute("data-onauth","onTelegramAuth(user)"),a.onerror=()=>setAuthStatus(t("telegram_auth_unavailable"),!0),e.appendChild(a)}function bindEmailLoginInput(){const e=document.getElementById("email-login-input");e&&(e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),requestEmailLoginCode())}),e.addEventListener("input",()=>{e.getAttribute("aria-invalid")==="true"&&(e.removeAttribute("aria-invalid"),setAuthStatus(""))}))}function bindEmailCodeInput(){const e=document.getElementById("email-login-code-input");e&&(e.addEventListener("input",()=>{e.value=sanitizeCode(e.value),updateEmailLoginCodeSlots()}),e.addEventListener("focus",updateEmailLoginCodeSlots),e.addEventListener("blur",updateEmailLoginCodeSlots),e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),sanitizeCode(e.value).length===6&&!state.emailLoginVerifying&&verifyEmailLoginCode())}))}function sanitizeCode(e){return String(e||"").replace(/\D/g,"").slice(0,6)}function isValidEmail(e){const n=normalizeEmail(e);return!!(n&&n.length<=254&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(n))}function readValidEmailLoginInput(){const e=document.getElementById("email-login-input"),n=normalizeEmail(e&&e.value);return!n||!isValidEmail(n)?(e&&(e.setAttribute("aria-invalid","true"),e.focus()),setAuthStatus(t(n?"email_invalid":"email_required"),!0),""):(e&&(e.value=n,e.removeAttribute("aria-invalid")),n)}function openEmailLoginCodeModal(e,n,a=!1){state.emailLoginEmail=e,state.emailLoginCodeModalOpen=!0;const i=document.getElementById("email-login-code-input");i&&(i.value=""),renderEmailLoginCodeModal(),setEmailCodeStatus(n||t("email_code_sent"),a),window.setTimeout(()=>{const o=document.getElementById("email-login-code-input");state.emailLoginCodeModalOpen&&o&&o.focus()},80)}function closeEmailLoginCodeModal(){state.emailLoginCodeModalOpen=!1;const e=document.getElementById("email-login-code-input");e&&(e.value=""),setEmailCodeStatus(""),renderEmailLoginCodeModal()}function renderEmailLoginCodeModal(){const e=document.getElementById("email-code-modal");if(!e)return;if(!state.emailLoginCodeModalOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.emailLoginCodeModalOpen||e.classList.add("hidden")},180);return}const n=document.getElementById("email-code-address");n&&(n.textContent=state.emailLoginEmail||"..."),e.classList.remove("hidden"),syncModalLock(),applyI18n(e),updateEmailLoginCodeSlots(),window.requestAnimationFrame(()=>e.classList.add("show"))}function updateEmailLoginCodeSlots(){const e=document.getElementById("email-login-code-input"),n=sanitizeCode(e&&e.value);e&&e.value!==n&&(e.value=n);for(let o=0;o<6;o+=1){const r=document.getElementById("email-code-slot-"+o);if(!r)continue;const l=n[o]||"";r.textContent=l,r.classList.toggle("filled",!!l),r.classList.toggle("active",state.emailLoginCodeModalOpen&&document.activeElement===e&&o===Math.min(n.length,5))}const a=document.getElementById("email-login-verify-btn");a&&(a.disabled=n.length!==6||state.emailLoginVerifying||state.emailLoginPending);const i=document.getElementById("email-login-resend-btn");i&&(i.disabled=state.emailLoginPending||state.emailLoginResending)}async function requestEmailLoginCode(){if(state.emailLoginPending)return;if(CFG.emailAuthEnabled===!1){setAuthStatus(t("email_auth_disabled"),!0);return}const e=readValidEmailLoginInput();if(e){state.emailLoginPending=!0,setButtonBusy("email-login-send-btn",!0),openEmailLoginCodeModal(e,t("email_code_sending")),setAuthStatus("");try{const n=await publicApi("/auth/email/request",{email:e,language:getLanguage()});if(!n.ok)throw n;setEmailCodeStatus(t("email_code_sent"))}catch(n){const a=emailErrorMessage(n,"email_code_send_failed");if(n&&n.error==="invalid_email"){closeEmailLoginCodeModal();const i=document.getElementById("email-login-input");i&&i.setAttribute("aria-invalid","true"),setAuthStatus(a,!0)}else setEmailCodeStatus(a,!0)}finally{state.emailLoginPending=!1,setButtonBusy("email-login-send-btn",!1),updateEmailLoginCodeSlots()}}}async function resendEmailLoginCode(){if(state.emailLoginPending||state.emailLoginResending)return;const e=state.emailLoginEmail||normalizeEmail(document.getElementById("email-login-input").value);if(!e||!isValidEmail(e)){setEmailCodeStatus(t(e?"email_invalid":"email_required"),!0);return}state.emailLoginResending=!0,updateEmailLoginCodeSlots(),setEmailCodeStatus(t("email_code_sending"));try{const n=await publicApi("/auth/email/request",{email:e,language:getLanguage()});if(!n.ok)throw n;state.emailLoginEmail=e;const a=document.getElementById("email-login-code-input");a&&(a.value=""),setEmailCodeStatus(t("email_code_sent"))}catch(n){setEmailCodeStatus(emailErrorMessage(n,"email_code_send_failed"),!0)}finally{state.emailLoginResending=!1,updateEmailLoginCodeSlots()}}async function verifyEmailLoginCode(){const e=state.emailLoginEmail||normalizeEmail(document.getElementById("email-login-input").value),n=document.getElementById("email-login-code-input"),a=sanitizeCode(n&&n.value);if(!e||a.length!==6){setEmailCodeStatus(t("email_code_invalid"),!0),updateEmailLoginCodeSlots();return}state.emailLoginVerifying=!0,updateEmailLoginCodeSlots(),setEmailCodeStatus(t("telegram_auth_verifying"));try{const i=await publicApi("/auth/email/verify",{email:e,code:a,referral_code:state.referralParam||""});if(!i.ok||!i.token)throw i;setToken(i.token,i.csrf_token),closeEmailLoginCodeModal(),await loadData()}catch(i){clearToken(),setEmailCodeStatus(emailErrorMessage(i,"email_code_invalid"),!0)}finally{state.emailLoginVerifying=!1,updateEmailLoginCodeSlots()}}function startExternalAuth(e={}){const n=e.resetStatus!==!1;showLogin(),n&&setAuthStatus(""),renderAuthMode()}async function loadData(){const e=await api("/me");if(!e.ok)throw new Error(e.error||"load failed");state.data=e,setBrand(),applyI18n(),applyLegalLinks();const n=e.plans||[];state.selectedPlan=getDefaultPaymentPlan(n),state.payment=null,state.creatingPayment=!1,state.telegramLinkRendered=!1,render(),showApp()}function render(){renderSubscription(state.data.subscription),renderUserMenu(state.data.user||{}),renderAccount(state.data.user||{}),renderReferral(state.data.referral||{}),renderAccountMergeModal(),renderPaymentFlow()}function renderSubscription(e){const n=document.getElementById("sub-status"),a=document.getElementById("sub-status-text"),i=document.getElementById("remaining"),o=document.getElementById("end-date-caption");if(n.classList.toggle("active",!!e.active),a.textContent=e.active?t("subscription_active"):t("subscription_inactive"),e.active){const r=formatCompactRemaining(e.days_left,getLanguage())||e.remaining_text||"";i.textContent=r}else i.textContent=t("no_active_subscription");if(e.end_date_text){const r=e.active?"sub_ends_template":"sub_ended_template";o.textContent=t(r,{date:e.end_date_text}),o.classList.remove("hidden")}else e.active?(o.textContent=t("sub_no_expiry"),o.classList.remove("hidden")):(o.textContent="",o.classList.add("hidden"));renderTraffic(e),document.getElementById("connect-actions").classList.toggle("hidden",!e.connect_url&&!e.config_link)}function renderTraffic(e){const n=document.getElementById("traffic-bar"),a=document.getElementById("traffic-bar-fill"),i=document.getElementById("traffic");if(!n||!a||!i)return;const o=Number(e.traffic_limit_bytes),r=Number(e.traffic_used_bytes),l=Number.isFinite(o)&&o>0,s=Number.isFinite(r)&&r>=0,d=e.traffic_used||t("not_available"),c=e.traffic_limit||t("not_available");if(n.classList.remove("over","unlimited"),!l){n.classList.add("unlimited"),a.style.width="100%",i.textContent=t("traffic_unlimited"),n.setAttribute("aria-valuenow","0");return}const u=s?r/o:0,m=Math.max(0,Math.min(100,Math.round(u*100)));a.style.width=m+"%",i.textContent=d+" / "+c,n.setAttribute("aria-valuenow",String(m)),s&&r>=o&&n.classList.add("over")}function formatCompactRemaining(e,n){const a=Math.max(0,Math.floor(Number(e)||0));if(a<=0)return null;if(a>=365){const i=Math.round(a/365*2)/2;return formatDecimal(i,n)+" "+pluralizeUnit(i,"year",n)}if(a>=30){const i=Math.round(a/30*2)/2;return formatDecimal(i,n)+" "+pluralizeUnit(i,"month",n)}return formatDecimal(a,n)+" "+pluralizeUnit(a,"day",n)}function formatDecimal(e,n){if(Number.isInteger(e))return String(e);const a=e.toFixed(1);return n==="ru"?a.replace(".",","):a}function pluralizeUnit(e,n,a){const i={year:["year","years"],month:["month","months"],day:["day","days"]},o={year:["год","года","лет"],month:["месяц","месяца","месяцев"],day:["день","дня","дней"]};if(a==="en")return e===1?i[n][0]:i[n][1];const r=o[n];if(!Number.isInteger(e))return r[1];const l=Math.abs(e),s=l%10,d=l%100;return s===1&&d!==11?r[0]:s>=2&&s<=4&&(d<12||d>14)?r[1]:r[2]}function renderUserMenu(e){const n=!!(e.email&&e.email_verified),a=!!e.telegram_linked,i=getUserDisplayName(e),o=getUserSecondaryText(e,n,a),r=getUserAvatarSrc(e),l=buildIdenticon(getUserAvatarSeed(e,n)),s=document.getElementById("user-chip-name"),d=document.getElementById("user-chip-avatar"),c=document.getElementById("user-dropdown-avatar"),u=document.getElementById("user-dropdown-name"),m=document.getElementById("user-dropdown-sub"),p=document.getElementById("user-dropdown-email-status"),f=document.getElementById("user-dropdown-telegram-status");s.textContent=i,setAvatarWithFallback(d,r,l,i),setAvatarWithFallback(c,r,l,i),u.textContent=i,m.textContent=o,m.classList.toggle("hidden",!o),p.textContent=n?e.email:t("not_linked"),p.classList.toggle("linked",n),p.classList.toggle("unlinked",!n),f.textContent=a?e.telegram_id?String(e.telegram_id):t("linked"):t("not_linked"),f.classList.toggle("linked",a),f.classList.toggle("unlinked",!a)}function renderAccount(e){const n=!!(e.email&&e.email_verified),a=!!e.telegram_linked,i=!!(state.data.settings&&state.data.settings.email_auth_enabled),o=!n&&i,r=!a,l=document.getElementById("email-link-box"),s=document.getElementById("telegram-link-box"),d=document.getElementById("link-panel");l.classList.toggle("hidden",!o),s.classList.toggle("hidden",!r),d.classList.toggle("hidden",!o&&!r),a?state.telegramLinkRendered=!1:renderTelegramLinkWidget()}function getTelegramInitUser(){return!tg||!tg.initDataUnsafe||!tg.initDataUnsafe.user?null:tg.initDataUnsafe.user}function getUserAvatarSeed(e,n){const a=getTelegramInitUser();return n&&e&&e.email?e.email.trim().toLowerCase():String(e&&e.id||e&&e.telegram_id||a&&a.id||"guest")}function getUserAvatarSrc(e){const n=getTelegramInitUser();return n&&n.photo_url?n.photo_url:e&&e.telegram_photo_url?e.telegram_photo_url:""}function getUserDisplayName(e){const n=getTelegramInitUser();return n&&n.first_name?n.first_name:e&&e.first_name?e.first_name:e&&e.username?"@"+e.username:n&&n.username?"@"+n.username:e&&e.email?e.email:t("guest_user")}function getUserSecondaryText(e,n,a){return n&&e.email?e.email:a&&e.telegram_id?"#"+e.telegram_id:""}function setAvatarWithFallback(e,n,a,i){if(e){if(e.alt=i||"",!n){e.onerror=null,e.src=a;return}e.onerror=()=>{e.onerror=null,e.src=a},e.src=n}}function buildIdenticon(e){const n=String(e||"guest");let a=2166136261;for(let c=0;c>>0;const i=a%360,o="hsl("+i+", 60%, 18%)",r="hsl("+(i+180)%360+", 78%, 62%)";let l="",s=a;for(let c=0;c<5;c++)for(let u=0;u<3;u++){const m=s&1;s=s>>>1^(s&1?46080:0),m&&(l+='',u!==2&&(l+=''))}const d=''+l+"";return"data:image/svg+xml;utf8,"+encodeURIComponent(d)}function toggleUserMenu(e){const n=typeof e=="boolean"?e:!state.userMenuOpen;state.userMenuOpen=n;const a=document.getElementById("user-chip"),i=document.getElementById("user-dropdown");if(!(!a||!i)){if(state.userMenuCloseTimer&&(window.clearTimeout(state.userMenuCloseTimer),state.userMenuCloseTimer=null),a.setAttribute("aria-expanded",n?"true":"false"),n){i.classList.remove("hidden"),i.classList.remove("show"),window.requestAnimationFrame(()=>{state.userMenuOpen&&i.classList.add("show")});return}i.classList.remove("show"),state.userMenuCloseTimer=window.setTimeout(()=>{state.userMenuOpen||i.classList.add("hidden"),state.userMenuCloseTimer=null},180)}}function handleDocumentClickForUserMenu(e){if(!state.userMenuOpen)return;const n=document.querySelector(".user-menu");n&&n.contains(e.target)||toggleUserMenu(!1)}function handleDocumentKeyForUserMenu(e){e.key==="Escape"&&state.userMenuOpen&&toggleUserMenu(!1),e.key==="Escape"&&state.langMenuOpen&&toggleLangMenu(!1)}function renderLangMenu(){const e=getLanguage(),n=document.getElementById("lang-chip-label"),a=document.getElementById("lang-chip-flag"),i=LANG_OPTIONS.find(r=>r.code===e)||LANG_OPTIONS[0];n&&(n.textContent=i.short),a&&(a.textContent=i.flag);const o=document.getElementById("lang-dropdown");o&&(o.innerHTML=LANG_OPTIONS.map(r=>'").join(""),o.querySelectorAll("[data-lang]").forEach(r=>{r.addEventListener("click",()=>setLanguage(r.dataset.lang))}))}function toggleLangMenu(e){const n=typeof e=="boolean"?e:!state.langMenuOpen;state.langMenuOpen=n;const a=document.getElementById("lang-chip"),i=document.getElementById("lang-dropdown");if(!(!a||!i)){if(state.langMenuCloseTimer&&(window.clearTimeout(state.langMenuCloseTimer),state.langMenuCloseTimer=null),a.setAttribute("aria-expanded",n?"true":"false"),n){i.classList.remove("hidden"),i.classList.remove("show"),window.requestAnimationFrame(()=>{state.langMenuOpen&&i.classList.add("show")});return}i.classList.remove("show"),state.langMenuCloseTimer=window.setTimeout(()=>{state.langMenuOpen||i.classList.add("hidden"),state.langMenuCloseTimer=null},180)}}function handleDocumentClickForLangMenu(e){if(!state.langMenuOpen)return;const n=document.querySelector(".lang-menu");n&&n.contains(e.target)||toggleLangMenu(!1)}function handleDocumentActionClick(e){const n=e.target instanceof Element?e.target.closest("[data-action]"):null;if(!n||!document.contains(n)||n.disabled)return;const a=String(n.dataset.action||"");if(a){switch(a){case"toggle-user-menu":toggleUserMenu();break;case"toggle-lang-menu":toggleLangMenu();break;case"logout":logout();break;case"open-connect-link":openConnectLink();break;case"toggle-payment-flow":togglePaymentFlow();break;case"open-referral-modal":openReferralModal();break;case"open-promo-modal":openPromoModal();break;case"close-payment-flow":closePaymentFlow();break;case"close-promo-modal":closePromoModal();break;case"close-referral-modal":closeReferralModal();break;case"close-account-merge-modal":closeAccountMergeModal();break;case"close-email-login-code-modal":closeEmailLoginCodeModal();break;case"request-email-link-code":requestEmailLinkCode();break;case"verify-email-link-code":verifyEmailLinkCode();break;case"request-email-login-code":requestEmailLoginCode();break;case"verify-email-login-code":verifyEmailLoginCode();break;case"resend-email-login-code":resendEmailLoginCode();break;case"set-auth-mode":setAuthMode(n.dataset.mode||"email");break;case"apply-promo-code":applyPromoCode();break;case"select-plan":selectPlan(n.dataset.months||"");break;case"create-payment":createAndOpenPayment(n.dataset.method||"");break;case"copy-referral-link":copyReferralLink(n.dataset.kind||"webapp");break;case"copy-config-link":copyConfigLink();break;case"check-payment":checkPayment();break;default:return}e.preventDefault()}}function openPromoModal(){state.promoModalOpen=!0,clearPromoStatus(),renderPromoModal(),window.setTimeout(()=>{const e=document.getElementById("promo-code-input");e&&e.focus()},80)}function closePromoModal(){state.promoModalOpen=!1,clearPromoStatus(),renderPromoModal()}function renderPromoModal(){const e=document.getElementById("promo-modal");if(e){if(!state.promoModalOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.promoModalOpen||e.classList.add("hidden")},180);return}e.classList.remove("hidden"),syncModalLock(),renderPromoStatus(),applyI18n(e),window.requestAnimationFrame(()=>e.classList.add("show"))}}function bindPromoInput(){const e=document.getElementById("promo-code-input");e&&(e.addEventListener("input",()=>{e.getAttribute("aria-invalid")==="true"&&e.removeAttribute("aria-invalid"),state.promoStatusText&&clearPromoStatus()}),e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),applyPromoCode())}))}function setPromoStatus(e,n="error"){state.promoStatusType=n==="success"?"success":"error",state.promoStatusText=normalizeStatusText(e||""),renderPromoStatus()}function clearPromoStatus(){state.promoStatusType="",state.promoStatusText="",renderPromoStatus()}function renderPromoStatus(){const e=document.getElementById("promo-status");if(e){if(!state.promoStatusText){e.textContent="",e.classList.add("hidden"),e.classList.remove("error","success");return}e.textContent=state.promoStatusText,e.classList.remove("hidden"),e.classList.toggle("error",state.promoStatusType==="error"),e.classList.toggle("success",state.promoStatusType==="success")}}function normalizeStatusText(e){const n=String(e||""),a=document.createElement("div");return a.innerHTML=n,(a.textContent||a.innerText||"").trim()}function openReferralModal(){state.referralModalOpen=!0,renderReferral(state.data&&state.data.referral||{})}function closeReferralModal(){state.referralModalOpen=!1,renderReferral(state.data&&state.data.referral||{})}function closeToolModals(){state.promoModalOpen=!1,state.referralModalOpen=!1,state.accountMergeModalOpen=!1,clearPromoStatus(),renderPromoModal(),renderReferral(state.data&&state.data.referral||{}),renderAccountMergeModal()}function openAccountMergeModal(e=null){e&&(state.accountMergeNotice=e),state.accountMergeModalOpen=!0,renderAccountMergeModal()}function closeAccountMergeModal(){state.accountMergeModalOpen=!1,renderAccountMergeModal()}function renderAccountMergeModal(){const e=document.getElementById("account-merge-modal"),n=document.getElementById("account-merge-panel");if(!e||!n)return;if(!state.accountMergeModalOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.accountMergeModalOpen||e.classList.add("hidden")},180);return}e.classList.remove("hidden"),syncModalLock();const a=state.accountMergeNotice||{},i=a.primary_user_id!=null?String(a.primary_user_id):t("not_available"),o=a.removed_user_id!=null?String(a.removed_user_id):t("not_available"),r=a.final_end_date_text||t("not_available");n.innerHTML=`
${escapeHtml(t("account_merge_title"))}