Files
remnawave-minishop/bot/app/web/templates/subscription_webapp.min.2191e637.js
T

80 lines
62 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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",paymentMethodCardPlategaSbp:"payment-method-card--platega payment-method-card--platega-sbp",paymentMethodCardPlategaCrypto:"payment-method-card--platega payment-method-card--platega-crypto",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_platega_sbp_button:"Оплата через СБП",pay_with_platega_crypto_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_platega_sbp_button:"Pay via SBP",pay_with_platega_crypto_button:"Pay with crypto",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(){const e=readJsonScript("i18n")||MOCK&&MOCK.i18n||{},n={};return new Set(Object.keys(FALLBACK_I18N).concat(Object.keys(e))).forEach(function(i){n[i]=Object.assign({},FALLBACK_I18N[i]||{},e[i]||{})}),n})(),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 m=s?r/o:0,u=Math.max(0,Math.min(100,Math.round(m*100)));a.style.width=u+"%",i.textContent=d+" / "+c,n.setAttribute("aria-valuenow",String(u)),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"),m=document.getElementById("user-dropdown-name"),u=document.getElementById("user-dropdown-sub"),p=document.getElementById("user-dropdown-email-status"),g=document.getElementById("user-dropdown-telegram-status");s.textContent=i,setAvatarWithFallback(d,r,l,i),setAvatarWithFallback(c,r,l,i),m.textContent=i,u.textContent=o,u.classList.toggle("hidden",!o),p.textContent=n?e.email:t("not_linked"),p.classList.toggle("linked",n),p.classList.toggle("unlinked",!n),g.textContent=a?e.telegram_id?String(e.telegram_id):t("linked"):t("not_linked"),g.classList.toggle("linked",a),g.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<n.length;c++)a^=n.charCodeAt(c),a=Math.imul(a,16777619)>>>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 m=0;m<3;m++){const u=s&1;s=s>>>1^(s&1?46080:0),u&&(l+='<rect x="'+m+'" y="'+c+'" width="1" height="1"/>',m!==2&&(l+='<rect x="'+(4-m)+'" y="'+c+'" width="1" height="1"/>'))}const d='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 5" shape-rendering="crispEdges"><rect width="5" height="5" fill="'+o+'"/><g fill="'+r+'">'+l+"</g></svg>";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=>'<button type="button" role="menuitem" class="lang-dropdown-item'+(r.code===e?" is-active":"")+'" data-lang="'+r.code+'"><span class="lang-dropdown-flag" aria-hidden="true">'+r.flag+'</span><span class="lang-dropdown-label">'+escapeHtml(r.label)+"</span></button>").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=`
<div class="${TW.panelHead}">
<div>
<div id="account-merge-title" class="${TW.sectionTitle} text-[var(--accent)]">${escapeHtml(t("account_merge_title"))}</div>
<div id="account-merge-caption" class="${TW.flowCaption}">${escapeHtml(t("account_merge_caption"))}</div>
</div>
<button class="${TW.iconBtn}" type="button" data-title-i18n="close" data-action="close-account-merge-modal">×</button>
</div>
<div class="notice grid gap-2">
<div class="text-[13px] leading-[1.5] text-[var(--text-secondary)]">${escapeHtml(t("account_merge_body"))}</div>
</div>
<div class="grid gap-2">
<div class="metric rounded-[var(--radius-md)] grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
<div class="metric-label">${escapeHtml(t("account_merge_primary_account_label"))}</div>
<div class="metric-value">#${escapeHtml(i)}</div>
</div>
<div class="metric rounded-[var(--radius-md)] grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
<div class="metric-label">${escapeHtml(t("account_merge_removed_account_label"))}</div>
<div class="metric-value">#${escapeHtml(o)}</div>
</div>
<div class="metric rounded-[var(--radius-md)] grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
<div class="metric-label">${escapeHtml(t("account_merge_end_date_label"))}</div>
<div class="metric-value">${escapeHtml(r)}</div>
</div>
</div>
`,applyI18n(e),window.requestAnimationFrame(()=>e.classList.add("show"))}function renderReferral(e){const n=document.getElementById("referral-modal"),a=document.getElementById("referral-panel");if(!n||!a)return;if(!state.referralModalOpen){n.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.referralModalOpen||n.classList.add("hidden")},180);return}if(n.classList.remove("hidden"),syncModalLock(),!state.referralModalOpen)return;const i=(e.bonus_details||[]).map(o=>`
<div class="${TW.bonusRow}">
<span>${escapeHtml(o.title||"")}</span>
<strong>${escapeHtml(t("referral_bonus_pair",{inviter:o.inviter_days||0,friend:o.friend_days||0}))}</strong>
</div>
`).join("");a.innerHTML=`
<div class="${TW.panelHead}">
<div>
<div class="${TW.sectionTitle} text-[var(--accent)]">${escapeHtml(t("referral_title"))}</div>
<div class="${TW.flowCaption}">${escapeHtml(t("referral_caption"))}</div>
</div>
<button class="${TW.iconBtn}" type="button" data-title-i18n="close" data-action="close-referral-modal">×</button>
</div>
<div class="grid gap-4 mt-1">
<div class="grid gap-2.5">
<div class="section-label font-[family-name:var(--font-mono)] text-[12px] font-extrabold text-[var(--accent)] uppercase">ВАШИ ССЫЛКИ</div>
<div class="referral-link-list grid gap-2">
${renderReferralLinkRow("webapp",t("referral_site_label"),e.webapp_link)}
${renderReferralLinkRow("bot",t("referral_telegram_label"),e.bot_link)}
</div>
</div>
<div class="referral-bonus-card grid gap-3 rounded-[var(--radius-md)] border border-[var(--border)] bg-[rgba(255,255,255,0.015)] p-3.5">
<div class="grid gap-1">
<div class="section-label font-[family-name:var(--font-mono)] text-[12px] font-extrabold text-[var(--accent)] uppercase">${escapeHtml(t("referral_bonus_title"))}</div>
<div class="text-[13px] leading-[1.45] text-[var(--text-secondary)]">${escapeHtml(t("referral_bonus_explanation"))}</div>
</div>
<div class="bonus-list grid gap-2">${i||'<div class="'+TW.empty+' border-none bg-[rgba(255,255,255,0.03)]">'+escapeHtml(t("referral_no_bonuses"))+"</div>"}</div>
</div>
</div>
`,applyI18n(n),window.requestAnimationFrame(()=>n.classList.add("show"))}function renderReferralLinkRow(e,n,a){return`
<div class="${TW.referralLinkRow}">
<div>
<div class="${TW.metricLabel}">${escapeHtml(n)}</div>
<div class="${TW.referralLinkValue}">${escapeHtml(a||t("not_available"))}</div>
</div>
<button class="${TW.btnBase} w-12 px-0" type="button" data-action="copy-referral-link" data-kind="${escapeAttr(e)}" ${a?"":"disabled"} data-title-i18n="copy_link">
<svg class="h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" aria-hidden="true">
<path d="M352 512L128 512L128 288L176 288L176 224L128 224C92.7 224 64 252.7 64 288L64 512C64 547.3 92.7 576 128 576L352 576C387.3 576 416 547.3 416 512L416 464L352 464L352 512zM288 416L512 416C547.3 416 576 387.3 576 352L576 128C576 92.7 547.3 64 512 64L288 64C252.7 64 224 92.7 224 128L224 352C224 387.3 252.7 416 288 416z"/>
</svg>
</button>
</div>
`}async function applyPromoCode(){if(state.promoApplying)return;const e=document.getElementById("promo-code-input"),n=String(e&&e.value||"").trim();if(!n){setPromoStatus(t("promo_empty"),"error"),e&&e.setAttribute("aria-invalid","true"),e&&e.focus();return}state.promoApplying=!0,setButtonBusy("promo-apply-btn",!0),clearPromoStatus(),e&&e.removeAttribute("aria-invalid");try{const a=await api("/promo/apply",{method:"POST",body:JSON.stringify({code:n})});if(!a.ok)throw a;e&&(e.value=""),setPromoStatus(t("promo_applied_ok"),"success"),await loadData()}catch(a){const i=a&&a.error,o=a&&(a.message||a.detail);e&&e.setAttribute("aria-invalid","true"),setPromoStatus(i==="empty_code"?t("promo_empty"):o||t(i==="promo_apply_failed"?"promo_invalid":"payment_error"),"error")}finally{state.promoApplying=!1,setButtonBusy("promo-apply-btn",!1)}}async function copyReferralLink(e){const n=state.data&&state.data.referral,a=n&&(e==="bot"?n.bot_link:n.webapp_link);if(!a){showToast(t("no_link"));return}await copyText(a),showToast(t("referral_copied"))}async function copyReferralCode(){const e=state.data&&state.data.referral,n=e&&e.code;if(!n){showToast(t("no_link"));return}await copyText(n),showToast(t("code_copied"))}async function requestEmailLinkCode(){const e=document.getElementById("email-link-input"),n=normalizeEmail(e.value);if(!n){showToast(t("email_code_send_failed"));return}state.emailLinkPending=!0,setButtonBusy("email-link-send-btn",!0);try{const a=await api("/account/email/request",{method:"POST",body:JSON.stringify({email:n})});if(!a.ok)throw a;if(a.already_linked){showToast(t("email_linked")),await loadData();return}state.emailLinkEmail=n,document.getElementById("email-link-code-row").classList.remove("hidden"),showToast(t("email_code_sent"))}catch(a){showToast(emailErrorMessage(a,"email_code_send_failed"))}finally{state.emailLinkPending=!1,setButtonBusy("email-link-send-btn",!1)}}async function verifyEmailLinkCode(){const e=state.emailLinkEmail||normalizeEmail(document.getElementById("email-link-input").value),n=document.getElementById("email-link-code-input").value;setButtonBusy("email-link-verify-btn",!0);let a=null;try{const i=await api("/account/email/verify",{method:"POST",body:JSON.stringify({email:e,code:n})});if(!i.ok)throw i;i.token&&setToken(i.token,i.csrf_token),a=i.account_merge&&i.account_merge.merged?i.account_merge:null,state.accountMergeNotice=a,showToast(t(a?"account_merge_toast":"email_linked")),state.emailLinkEmail="";try{await loadData(),a&&openAccountMergeModal(a)}catch(o){console.warn("Email account linked, but data refresh failed",o),a&&openAccountMergeModal(a)}}catch(i){showToast(emailErrorMessage(i,"email_code_invalid"))}finally{setButtonBusy("email-link-verify-btn",!1)}}function renderTelegramLinkWidget(){const e=document.getElementById("telegram-link-widget");if(!e||state.telegramLinkRendered)return;e.innerHTML="";const n=String(CFG.telegramLoginBotUsername||"").trim();if(!n){setTelegramLinkStatus(t("telegram_auth_unavailable"),!0);return}window.onTelegramLinkAuth=async function(i){await linkTelegramAccount(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","onTelegramLinkAuth(user)"),a.onerror=()=>setTelegramLinkStatus(t("telegram_auth_unavailable"),!0),e.appendChild(a),state.telegramLinkRendered=!0}async function linkTelegramAccount(e){if(!(!e||state.telegramLinkInProgress)){state.telegramLinkInProgress=!0,setTelegramLinkStatus(t("telegram_auth_verifying"));try{const n=await api("/account/telegram/link",{method:"POST",body:JSON.stringify({auth_data:e})});if(!n.ok)throw n;await finishTelegramLink(n,e.id)}catch(n){if(await refreshLinkedTelegramAfterError()){setTelegramLinkStatus("");try{showToast(t("telegram_linked"))}catch(a){console.warn("Telegram link success toast failed",a)}return}setTelegramLinkStatus(emailErrorMessage(n,"telegram_auth_failed"),!0)}finally{state.telegramLinkInProgress=!1}}}async function finishTelegramLink(e,n){e.token&&setToken(e.token,e.csrf_token),markTelegramLinked(e.telegram_id||n),setTelegramLinkStatus(""),state.telegramLinkRendered=!1;const a=e.account_merge&&e.account_merge.merged?e.account_merge:null;state.accountMergeNotice=a;try{showToast(t(a?"account_merge_toast":"telegram_linked"))}catch(i){console.warn("Telegram link success toast failed",i)}try{await loadData(),a&&openAccountMergeModal(a)}catch(i){console.warn("Telegram account linked, but data refresh failed",i),a&&openAccountMergeModal(a)}}function markTelegramLinked(e){!state.data||!state.data.user||(state.data.user.telegram_linked=!0,e&&(state.data.user.telegram_id=Number(e)),renderAccount(state.data.user))}async function refreshLinkedTelegramAfterError(){try{return await loadData(),!!(state.data&&state.data.user&&state.data.user.telegram_linked)}catch(e){return console.warn("Telegram link status refresh failed",e),!1}}function togglePaymentFlow(){if(state.paymentFlowOpen){closePaymentFlow();return}state.paymentFlowOpen=!0,state.creatingPayment=!1,state.payment=null,state.selectedPlan=getDefaultPaymentPlan(state.data&&state.data.plans||[]),renderPaymentFlow()}function closePaymentFlow(){state.paymentFlowOpen=!1,state.creatingPayment=!1,state.payment=null,renderPaymentFlow()}function renderPaymentFlow(){const e=document.getElementById("payment-modal");if(e){if(!state.paymentFlowOpen){e.classList.remove("show"),syncModalLock(),window.setTimeout(()=>{state.paymentFlowOpen||e.classList.add("hidden")},180);return}e.classList.remove("hidden"),syncModalLock(),window.requestAnimationFrame(()=>e.classList.add("show")),renderPlans(state.data&&state.data.plans||[]),renderPaymentSummary(),renderMethods(),applyI18n(e)}}function syncModalLock(){document.body.classList.toggle("modal-open",!!(state.paymentFlowOpen||state.emailLoginCodeModalOpen||state.promoModalOpen||state.referralModalOpen||state.accountMergeModalOpen))}function getDefaultPaymentPlan(e=[]){return e.length&&(e.find(n=>Number(n.months)===3)||e[0])||null}function renderPlans(e){const n=document.getElementById("plans");if(n){if(!e.length){n.innerHTML='<div class="'+TW.empty+'">'+escapeHtml(t("no_plans"))+"</div>";return}(!state.selectedPlan||!e.some(a=>Number(a.months)===Number(state.selectedPlan.months)))&&(state.selectedPlan=getDefaultPaymentPlan(e)),n.innerHTML=e.map(a=>{const i=Number(a.months),o=state.selectedPlan&&Number(state.selectedPlan.months)===i;return`
<button class="${TW.planCard} ${o?TW.planCardActive:""}" type="button" data-action="select-plan" data-months="${i}" ${state.creatingPayment?"disabled":""}>
${escapeHtml(a.title)}
</button>
`}).join("")}}function renderPaymentSummary(){const e=document.getElementById("selected-plan-price");if(e){if(!state.selectedPlan){e.textContent=t("no_plans");return}e.textContent=formatMoney(state.selectedPlan.price,state.selectedPlan.currency)}}function renderMethods(){const e=document.getElementById("payment-methods");if(!e)return;if(!state.selectedPlan){e.innerHTML='<div class="'+TW.empty+'">'+escapeHtml(t("choose_plan_first"))+"</div>";return}const n=getAvailableMethods();if(!n.length){e.innerHTML='<div class="'+TW.empty+'">'+escapeHtml(t("no_methods"))+"</div>";return}e.innerHTML=n.map(a=>{let i,o;return a.id==="platega_sbp"?(i="pay_with_platega_sbp_button",o=TW.paymentMethodCardPlategaSbp):a.id==="platega_crypto"?(i="pay_with_platega_crypto_button",o=TW.paymentMethodCardPlategaCrypto):a.id==="platega"?(i="pay_with_platega_button",o=TW.paymentMethodCardPlatega):(i="pay_with_cryptopay_button",o=TW.paymentMethodCardCryptopay),`
<button class="${TW.paymentMethodCard} ${o}" type="button" data-action="create-payment" data-method="${escapeAttr(a.id)}" data-i18n="${i}" ${state.creatingPayment?"disabled":""}>
${escapeHtml(t(i))}
</button>
`}).join("")}function selectPlan(e){state.creatingPayment||(state.selectedPlan=(state.data&&state.data.plans||[]).find(n=>Number(n.months)===Number(e))||null,state.payment=null,renderPaymentFlow())}async function createAndOpenPayment(e){if(state.creatingPayment)return;if(!state.selectedPlan){showToast(t("choose_plan_toast"));return}if(!getAvailableMethods().some(a=>a.id===e)){showToast(t("no_methods"));return}state.creatingPayment=!0,state.payment=null,renderPaymentFlow();try{const a=await api("/payments",{method:"POST",body:JSON.stringify({months:state.selectedPlan.months,method:e})});if(!a.ok||!a.payment_url)throw new Error(a.message||t("payment_error"));state.payment=a,openPaymentUrl(a.payment_url)&&closePaymentFlow()}catch(a){showToast(a.message||t("payment_error"))}finally{state.creatingPayment=!1,state.paymentFlowOpen&&renderPaymentFlow()}}function getAvailableMethods(){if(!state.data||!state.selectedPlan)return[];const e=new Map((state.data.payment_methods||[]).map(n=>[String(n.id||"").toLowerCase(),n]));return["platega_sbp","platega_crypto","platega","cryptopay"].map(n=>e.get(n)).filter(Boolean)}function openPaymentUrl(e){const n=String(e||state.payment&&state.payment.payment_url||"").trim();return n?tg&&tg.openLink?(tg.openLink(n),!0):(window.open(n,"_blank","noopener")||window.location.assign(n),!0):!1}async function checkPayment(){if(!state.payment||!state.payment.payment_id)return;(await api("/payments/"+state.payment.payment_id)).paid?(showToast(t("payment_confirmed")),state.paymentFlowOpen=!1,await loadData()):showToast(t("payment_pending"))}function openConnectLink(){const e=state.data&&state.data.subscription,n=e&&(e.connect_url||e.config_link);if(!n){showToast(t("no_link"));return}tg&&tg.openLink?tg.openLink(n):window.open(n,"_blank","noopener")}async function copyConfigLink(){const e=state.data&&state.data.subscription,n=e&&e.config_link;if(!n){showToast(t("no_link"));return}await copyText(n),showToast(t("link_copied"))}async function copyText(e){try{await navigator.clipboard.writeText(e)}catch(n){const a=document.createElement("textarea");a.value=e,document.body.appendChild(a),a.select(),document.execCommand("copy"),a.remove()}}async function api(e,n={}){if(MOCK)return mockApi(e,n);const a=String(n.method||"GET").toUpperCase(),i=Object.assign({},n.headers||{});state.token&&(i.Authorization="Bearer "+state.token);const o=state.csrfToken||readCookie("rw_webapp_csrf")||"";o&&["POST","PUT","PATCH","DELETE"].includes(a)&&(i["X-CSRF-Token"]=o),n.body&&!i["Content-Type"]&&(i["Content-Type"]="application/json");const r=await fetch(CFG.apiBase+e,Object.assign({},n,{headers:i}));if(r.status===401)throw clearToken(),await startExternalAuth(),new Error("Unauthorized");return r.json()}async function publicApi(e,n={}){return MOCK?mockApi(e,{method:"POST",body:JSON.stringify(n)}):(await fetch(CFG.apiBase+e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function mockApi(e,n={}){return await new Promise(a=>window.setTimeout(a,120)),e==="/me"?JSON.parse(JSON.stringify(MOCK.data)):e==="/auth/email/request"||e==="/account/email/request"?{ok:!0}:e==="/auth/email/verify"?{ok:!0,token:"local-preview",csrf_token:"local-preview-csrf"}:e==="/account/email/verify"?(MOCK.data.user.email="preview@example.com",MOCK.data.user.email_verified=!0,{ok:!0,token:"local-preview",csrf_token:"local-preview-csrf"}):e==="/account/telegram/link"?(MOCK.data.user.telegram_linked=!0,MOCK.data.user.telegram_id=100200300,{ok:!0,token:"local-preview",csrf_token:"local-preview-csrf"}):e==="/auth/logout"?{ok:!0}:e==="/promo/apply"?{ok:!0,end_date_text:"20.05.2026 12:00"}:e==="/payments"&&String(n.method||"").toUpperCase()==="POST"?{ok:!0,action:"open_link",payment_url:"https://example.com/payment-preview",payment_id:10001}:e.startsWith("/payments/")?{ok:!0,payment_id:10001,status:"pending",paid:!1}:{ok:!1,error:"not_found"}}function setToken(e,n=""){state.token=e,state.csrfToken=n||readCookie("rw_webapp_csrf")||state.csrfToken||""}function clearToken(){state.token="",state.csrfToken="",localStorage.removeItem("rw_webapp_token")}function readCookie(e){const n=e+"=",a=document.cookie.split("; ").find(i=>i.startsWith(n));return a?decodeURIComponent(a.slice(n.length)):""}function normalizeEmail(e){return String(e||"").trim().toLowerCase()}function emailErrorMessage(e,n){const a=e&&e.error;return a==="rate_limited"?t("email_rate_limited",{seconds:e.retry_after||60}):t(a==="invalid_email"?"email_invalid":a==="expired_code"?"email_code_expired":a==="invalid_code"||a==="too_many_attempts"?"email_code_invalid":a==="email_auth_not_configured"?"email_auth_disabled":a==="account_merge_conflict"?"account_merge_conflict":a==="invalid_auth"?"telegram_auth_failed":n)}function setButtonBusy(e,n){const a=document.getElementById(e);a&&(a.disabled=!!n)}function logout(){toggleUserMenu(!1),publicApi("/auth/logout",{}).catch(()=>{}),clearToken(),closePaymentFlow(),closeEmailLoginCodeModal(),startExternalAuth()}function showLoader(){document.getElementById("loader").classList.remove("hidden"),document.getElementById("app").classList.add("hidden"),document.getElementById("login").classList.remove("show")}function showApp(){document.getElementById("loader").classList.add("hidden"),document.getElementById("login").classList.remove("show"),document.getElementById("login").classList.add("hidden"),document.getElementById("app").classList.remove("hidden")}function showLogin(){document.getElementById("loader").classList.add("hidden"),document.getElementById("app").classList.add("hidden"),document.getElementById("login").classList.remove("hidden"),document.getElementById("login").classList.add("show")}function setAuthStatus(e,n=!1){const a=document.getElementById("auth-status");if(a){if(!e){a.textContent="",a.classList.add("hidden"),a.classList.remove("error");return}a.textContent=e,a.classList.remove("hidden"),a.classList.toggle("error",!!n)}}function setEmailCodeStatus(e,n=!1){const a=document.getElementById("email-code-status");if(a){if(!e){a.textContent="",a.classList.add("hidden"),a.classList.remove("error");return}a.textContent=e,a.classList.remove("hidden"),a.classList.toggle("error",!!n)}}function setTelegramLinkStatus(e,n=!1){const a=document.getElementById("telegram-link-status");if(a){if(!e){a.textContent="",a.classList.add("hidden"),a.classList.remove("error");return}a.textContent=e,a.classList.remove("hidden"),a.classList.toggle("error",!!n)}}function showToast(e){const n=document.getElementById("toast");if(n){if(n.textContent=e,n.classList.remove("hidden"),n.offsetWidth,n.classList.add("show"),tg&&tg.HapticFeedback)try{tg.HapticFeedback.notificationOccurred("success")}catch(a){console.warn("Haptic feedback failed",a)}state.toastTimer&&window.clearTimeout(state.toastTimer),state.toastTimer=window.setTimeout(()=>{n.classList.remove("show"),window.setTimeout(()=>{n.classList.contains("show")||n.classList.add("hidden")},250)},2200)}}function setFavicon(e){let n=document.getElementById("app-favicon");n||(n=document.createElement("link"),n.id="app-favicon",n.rel="icon",n.setAttribute("sizes","any"),document.head.appendChild(n)),n.href=e||"data:,"}function setBrandLogo(e,n){if(!e)return;const a=e.querySelector("[data-brand-logo]"),i=e.querySelector("[data-brand-logo-spinner]");if(!a)return;if(a.onload=null,a.onerror=null,a.removeAttribute("data-brand-logo-url"),!n){a.removeAttribute("src"),a.classList.add("hidden"),i&&i.classList.add("hidden"),e.classList.add("hidden");return}const o=n;e.classList.remove("hidden"),i&&i.classList.remove("hidden"),a.classList.add("hidden"),a.dataset.brandLogoUrl=o,a.onload=()=>{a.dataset.brandLogoUrl===o&&(a.classList.remove("hidden"),i&&i.classList.add("hidden"))},a.onerror=()=>{a.dataset.brandLogoUrl===o&&(a.removeAttribute("src"),a.classList.add("hidden"),i&&i.classList.add("hidden"),e.classList.add("hidden"))},a.src=n,a.complete&&a.naturalWidth>0&&(a.classList.remove("hidden"),i&&i.classList.add("hidden"))}function setBrand(){const e=CFG.title||document.title||t("page_title"),n=CFG.logoUrl||"";document.title=e,setFavicon(n),document.querySelectorAll("[data-brand-title]").forEach(a=>{a.textContent=e}),document.querySelectorAll("[data-brand-logo-shell]").forEach(a=>{setBrandLogo(a,n)})}function applyI18n(e=document){document.documentElement.lang=getLanguage(),e.querySelectorAll("[data-i18n]").forEach(n=>{n.textContent=t(n.dataset.i18n)}),e.querySelectorAll("[data-title-i18n]").forEach(n=>{const a=t(n.dataset.titleI18n);n.setAttribute("title",a),n.setAttribute("aria-label",a)}),e.querySelectorAll("[data-aria-i18n]").forEach(n=>{n.setAttribute("aria-label",t(n.dataset.ariaI18n))}),e.querySelectorAll("[data-placeholder-i18n]").forEach(n=>{n.setAttribute("placeholder",t(n.dataset.placeholderI18n))})}function applyLegalLinks(e=document){const n={privacyPolicyUrl:CFG.privacyPolicyUrl||"",userAgreementUrl:CFG.userAgreementUrl||""};e.querySelectorAll("[data-legal-key]").forEach(a=>{const i=a.dataset.legalKey,o=n[i]||"";a.href=o||"#",a.classList.toggle("hidden",!o),a.setAttribute("aria-hidden",o?"false":"true")}),e.querySelectorAll("[data-legal-links]").forEach(a=>{const i=Array.from(a.querySelectorAll("[data-legal-key]")).some(o=>!o.classList.contains("hidden"));a.classList.toggle("hidden",!i)})}function getLanguage(){const e=[];state.langOverride&&e.push(state.langOverride),state.data&&state.data.user&&state.data.user.language_code&&e.push(state.data.user.language_code);const n=tg&&tg.initDataUnsafe&&tg.initDataUnsafe.user;n&&n.language_code&&e.push(n.language_code),typeof navigator!="undefined"&&(Array.isArray(navigator.languages)&&navigator.languages.forEach(a=>a&&e.push(a)),navigator.language&&e.push(navigator.language)),CFG.language&&e.push(CFG.language),document.documentElement.lang&&e.push(document.documentElement.lang);for(const a of e){const i=String(a).toLowerCase().split("-")[0];if(I18N&&I18N[i])return i}return"ru"}function setLanguage(e){const n=String(e||"").toLowerCase().split("-")[0];if(!(I18N&&Object.keys(I18N).length&&!I18N[n])){state.langOverride=n;try{localStorage.setItem("rw_webapp_lang",n)}catch(a){}if(applyI18n(),state.data)try{render()}catch(a){}renderLangMenu(),toggleLangMenu(!1)}}function t(e,n={}){const a=I18N&&I18N[getLanguage()]||I18N&&I18N.ru||{},i=I18N&&I18N.ru&&I18N.ru[e]||e;return(a[e]||i).replace(/\{(\w+)\}/g,(r,l)=>Object.prototype.hasOwnProperty.call(n,l)?String(n[l]):"")}function formatMoney(e,n){const a=Number(e||0);return(Number.isInteger(a)?String(a):a.toFixed(2))+" "+(n||CFG.currency||"RUB")}function escapeHtml(e){return String(e==null?"":e).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#039;")}function escapeAttr(e){return escapeHtml(e).replaceAll("`","&#096;")}