diff --git a/bot/app/web/subscription_webapp.py b/bot/app/web/subscription_webapp.py index 5130000..39c7589 100644 --- a/bot/app/web/subscription_webapp.py +++ b/bot/app/web/subscription_webapp.py @@ -52,6 +52,7 @@ _UNPATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') _PATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') {\n origin = default_origin;\n } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') {\n origin = dev_origin;\n } else {\n origin = default_origin;\n }\n""" WEBAPP_CONFIG_PLACEHOLDER = "" WEBAPP_I18N_PLACEHOLDER = "" +WEBAPP_JS_PLACEHOLDER = "" DEV_MOCK_START_MARKER = "" DEV_MOCK_END_MARKER = "" WEBAPP_RATE_LIMIT_WINDOW_SECONDS = 60 @@ -117,6 +118,7 @@ def setup_subscription_webapp_routes(app: web.Application) -> None: app.router.add_get("/telegram-widget.js", telegram_widget_asset_route) app.router.add_get(WEBAPP_LOGO_PROXY_PATH, webapp_logo_route) app.router.add_get("/subscription_webapp.css", css_asset_route) + app.router.add_get("/subscription_webapp.min.{asset_hash}.js", js_asset_route) app.router.add_get("/subscription_webapp.js", js_asset_route) app.router.add_post("/api/auth/token", auth_token_route) app.router.add_post("/api/auth/email/request", email_auth_request_route) @@ -523,12 +525,22 @@ def _normalize_telegram_login_widget_sdk(data: bytes) -> bytes: async def js_asset_route(request: web.Request) -> web.Response: - return await _serve_template_asset( - request, - "subscription_webapp.js", - "application/javascript", - strip_dev_mock=True, + asset_hash = request.match_info.get("asset_hash") + filename = ( + f"subscription_webapp.min.{asset_hash}.js" + if asset_hash + else "subscription_webapp.js" ) + response = await _serve_template_asset( + request, + filename, + "application/javascript", + strip_dev_mock=not asset_hash, + ) + response.headers["Cache-Control"] = ( + "public, max-age=31536000, immutable" if asset_hash else "no-cache" + ) + return response async def index_route(request: web.Request) -> web.Response: @@ -571,6 +583,10 @@ async def index_route(request: web.Request) -> web.Response: + "" ), ) + html = html.replace( + WEBAPP_JS_PLACEHOLDER, + f'', + ) return web.Response(text=html, content_type="text/html", charset="utf-8") @@ -596,6 +612,19 @@ async def _serve_template_asset( return web.Response(text=text, content_type=content_type, charset="utf-8") +def _resolve_webapp_js_asset_name() -> str: + minified_assets = [] + for path in ASSET_DIR.glob("subscription_webapp.min.*.js"): + try: + minified_assets.append((path.stat().st_mtime, path.name)) + except OSError: + continue + if minified_assets: + minified_assets.sort(reverse=True) + return minified_assets[0][1] + return "subscription_webapp.js" + + def _strip_marked_block(html: str, start_marker: str, end_marker: str) -> str: start = html.find(start_marker) if start == -1: @@ -667,7 +696,7 @@ async def auth_token_route(request: web.Request) -> web.Response: await session.commit() except Exception as exc: await session.rollback() - logger.error("WebApp auth failed: %s", exc, exc_info=True) + logger.exception("WebApp auth failed") return _json_error(500, "auth_failed", "Auth failed") token = create_webapp_session_token(settings, int(authenticated_user_id)) @@ -759,7 +788,7 @@ async def email_auth_verify_route(request: web.Request) -> web.Response: await session.commit() except Exception as exc: await session.rollback() - logger.error("Email WebApp auth failed: %s", exc, exc_info=True) + logger.exception("Email WebApp auth failed") return _json_error(500, "auth_failed", "Auth failed") token = create_webapp_session_token(settings, int(db_user.user_id)) @@ -919,7 +948,7 @@ async def account_email_verify_route(request: web.Request) -> web.Response: return _json_error(409, "account_merge_conflict", str(exc)) except Exception as exc: await session.rollback() - logger.error("Email account link failed: %s", exc, exc_info=True) + logger.exception("Email account link failed") return _json_error(500, "link_failed", "Link failed") token = create_webapp_session_token(settings, int(final_user_id)) @@ -1043,7 +1072,7 @@ async def account_telegram_link_route(request: web.Request) -> web.Response: return _json_error(409, "account_merge_conflict", str(exc)) except Exception as exc: await session.rollback() - logger.error("Telegram account link failed: %s", exc, exc_info=True) + logger.exception("Telegram account link failed") return _json_error(500, "link_failed", "Link failed") token = create_webapp_session_token(settings, int(final_user_id)) @@ -1106,7 +1135,7 @@ async def apply_promo_route(request: web.Request) -> web.Response: ) except Exception as exc: await session.rollback() - logger.error("WebApp promo apply failed: %s", exc, exc_info=True) + logger.exception("WebApp promo apply failed") return _json_error(500, "promo_apply_failed", "Promo apply failed") @@ -1241,7 +1270,7 @@ async def _request_email_code( return web.json_response({"ok": True}) except Exception as exc: await session.rollback() - logger.error("Failed to send email verification code: %s", exc, exc_info=True) + logger.exception("Failed to send email verification code") return _json_error(502, "email_send_failed", "Failed to send email") @@ -2015,7 +2044,7 @@ async def _create_yookassa_payment( ) except Exception as exc: await session.rollback() - logger.error("YooKassa WebApp payment failed: %s", exc, exc_info=True) + logger.exception("YooKassa WebApp payment failed") return _json_error(502, "payment_failed", "Failed to create payment") @@ -2076,7 +2105,7 @@ async def _create_freekassa_payment( ) except Exception as exc: await session.rollback() - logger.error("FreeKassa WebApp payment failed: %s", exc, exc_info=True) + logger.exception("FreeKassa WebApp payment failed") return _json_error(502, "payment_failed", "Failed to create payment") @@ -2152,7 +2181,7 @@ async def _create_platega_payment( ) except Exception as exc: await session.rollback() - logger.error("Platega WebApp payment failed: %s", exc, exc_info=True) + logger.exception("Platega WebApp payment failed") return _json_error(502, "payment_failed", "Failed to create payment") @@ -2215,7 +2244,7 @@ async def _create_severpay_payment( ) except Exception as exc: await session.rollback() - logger.error("SeverPay WebApp payment failed: %s", exc, exc_info=True) + logger.exception("SeverPay WebApp payment failed") return _json_error(502, "payment_failed", "Failed to create payment") @@ -2278,7 +2307,7 @@ async def _create_stars_payment( ) except Exception as exc: await session.rollback() - logger.error("Stars WebApp payment failed: %s", exc, exc_info=True) + logger.exception("Stars WebApp payment failed") return _json_error(502, "payment_failed", "Failed to create invoice") diff --git a/bot/app/web/templates/subscription_webapp.css b/bot/app/web/templates/subscription_webapp.css index ab29f6b..9e4b0dd 100644 --- a/bot/app/web/templates/subscription_webapp.css +++ b/bot/app/web/templates/subscription_webapp.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-bold:700;--font-weight-extrabold:800;--tracking-wide:.025em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html,body{margin:calc(var(--spacing) * 0);--tw-tracking:0;letter-spacing:0;width:100%;min-height:100%;overflow-x:hidden}body{background-color:var(--bg-primary);font-family:var(--font-sans);color:var(--text-primary);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-tap-highlight-color:transparent;overflow-x:hidden}body.modal-open{overflow:hidden}button,a{font:inherit}button{cursor:pointer;border-style:var(--tw-border-style);border-width:0}button:disabled{cursor:progress;opacity:.58}}@layer components{.btn-sm{min-height:38px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:11px;padding-block:calc(var(--spacing) * 2);text-align:center;--tw-leading:1;--tw-font-weight:var(--font-weight-extrabold);font-size:13px;line-height:1;font-weight:var(--font-weight-extrabold);color:var(--text-secondary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.btn-sm:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn-sm:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.btn-sm:hover{background-color:var(--bg-card-hover)}}.metrics{gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:#ffffff05;display:grid;overflow:hidden}.metric{min-height:calc(var(--spacing) * 12);align-items:center;gap:calc(var(--spacing) * 2.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);background-color:#ffffff05;grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr);display:grid}.auth-tab{min-height:calc(var(--spacing) * 9);border-radius:var(--radius-sm);--tw-font-weight:850;color:var(--text-secondary);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#0000;font-weight:850}.login-layout{justify-content:center;align-items:center;gap:calc(var(--spacing) * 4);width:min(100vw,560px);min-height:100vh;padding-inline:14px;padding-top:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra));padding-bottom:max(var(--app-safe-bottom), 18px);flex-direction:column;margin-inline:auto;display:none}.section-label{font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-size:11px;font-weight:var(--font-weight-extrabold);color:var(--text-muted)}.app-loader{min-height:78vh;font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-muted);place-items:center;display:grid}.auth-tabs-wrapper{gap:calc(var(--spacing) * 1);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:46px;padding:calc(var(--spacing) * 1);background-color:#05080d61;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.panel-head{justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);display:flex}.section-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-leading:var(--leading-tight);line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.section-caption{color:var(--text-secondary);margin-top:3px;font-size:13px}.metric-label{min-width:calc(var(--spacing) * 0);font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:11px;font-weight:var(--font-weight-bold);color:var(--text-muted)}.metric-value{min-width:calc(var(--spacing) * 0);text-align:right;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--text-primary)}.referral-link-row{align-items:center;gap:calc(var(--spacing) * 2.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:58px;padding-inline:calc(var(--spacing) * 3);background-color:#ffffff05;grid-template-columns:minmax(0,1fr) 48px;padding-block:11px;display:grid}.referral-link-value{margin-top:calc(var(--spacing) * 1);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1.35;--tw-font-weight:var(--font-weight-bold);line-height:1.35;font-weight:var(--font-weight-bold);color:var(--text-primary)}.bonus-row{align-items:center;gap:calc(var(--spacing) * 2.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:58px;padding-inline:calc(var(--spacing) * 3);background-color:#ffffff05;grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);padding-block:11px;display:grid}.empty-state{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);background-color:#ffffff05;padding:13px;line-height:1.45}.plan-card{width:100%;min-height:44px;min-width:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;--tw-leading:var(--leading-tight);font-size:14px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#ffffff05;justify-content:center;align-items:center;display:inline-flex}@media (hover:hover){.plan-card:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.plan-card:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.plan-card:hover{background-color:var(--bg-card-hover)}}.plan-card{transition-duration:.2s}.plan-card-active{border-color:var(--accent);background-color:color-mix(in srgb,var(--accent) 8%,transparent);color:var(--accent);--tw-shadow:0 12px 30px var(--tw-shadow-color,#0003);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:color-mix(in srgb,var(--accent) 58%,transparent)}.payment-method-card{width:100%;min-height:52px;min-width:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 4);text-align:center;--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#ffffff08;justify-content:center;align-items:center;padding-block:12px;display:inline-flex}@media (hover:hover){.payment-method-card:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.payment-method-card:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.payment-method-card:hover{background-color:var(--bg-card-hover)}}.payment-method-card{transition-duration:.2s}.payment-method-card--platega{background:#00fe7a14;border-color:#00fe7a5c}.payment-method-card--cryptopay{background:#22d3ee14;border-color:#22d3ee5c}.plan-name{--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);display:block}.plan-meta{margin-top:calc(var(--spacing) * 1);font-family:var(--font-mono);--tw-leading:1.3;--tw-font-weight:var(--font-weight-bold);font-size:11px;line-height:1.3;font-weight:var(--font-weight-bold);color:var(--text-muted);display:block}.plan-price{text-align:right;max-width:48%;font-family:var(--font-mono);--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--accent);flex:none;display:block}.notice{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);background-color:#ffffff05;padding:13px;line-height:1.45}.step-num{font-family:var(--font-mono);--tw-leading:1.1;--tw-font-weight:var(--font-weight-extrabold);font-size:11px;line-height:1.1;font-weight:var(--font-weight-extrabold);color:currentColor;display:block}.step-name{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1.15;--tw-font-weight:var(--font-weight-extrabold);line-height:1.15;font-weight:var(--font-weight-extrabold);color:currentColor;display:block}.main-value{margin:calc(var(--spacing) * 0);--tw-leading:1.08;--tw-font-weight:var(--font-weight-extrabold);font-size:32px;line-height:1.08;font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.main-caption{margin-top:calc(var(--spacing) * 1.5);margin-bottom:calc(var(--spacing) * 0);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);line-height:1.45}.status-text{margin:calc(var(--spacing) * 0);min-height:calc(var(--spacing) * 5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);line-height:1.45}.legal-links{justify-content:center;column-gap:calc(var(--spacing) * 4);row-gap:calc(var(--spacing) * 1.5);text-align:center;flex-wrap:wrap;display:flex}.app-header{min-height:calc(var(--spacing) * 12);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.app-header-title{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2.5);display:flex}.app-header-actions{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.panel-head-sm{min-width:calc(var(--spacing) * 0);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2.5);display:flex}.user-menu,.lang-menu{display:inline-flex;position:relative}.lang-chip{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:38px;padding-inline:calc(var(--spacing) * 2);text-align:left;--tw-font-weight:var(--font-weight-extrabold);font-size:12px;font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.lang-chip:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.lang-chip:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.lang-chip:hover{background-color:var(--bg-card-hover)}}.lang-chip[aria-expanded=true]{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.lang-chip[aria-expanded=true]{border-color:color-mix(in srgb,var(--accent) 55%,var(--border))}}.lang-chip[aria-expanded=true]{background-color:var(--bg-card-hover)}.lang-chip-flag{--tw-leading:1;font-family:Twemoji Country Flags,Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif;font-size:14px;line-height:1}.lang-chip-label{font-family:var(--font-mono);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);font-size:11px}.lang-chip-caret{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);fill:var(--text-secondary);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));flex:none}.lang-chip[aria-expanded=true] .lang-chip-caret{transform:rotate(180deg)}.lang-dropdown{pointer-events:none;top:calc(100% + 8px);right:calc(var(--spacing) * 0);z-index:30;transform-origin:100% 0;--tw-translate-y:calc(var(--spacing) * -2);width:160px;translate:var(--tw-translate-x) var(--tw-translate-y);gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);opacity:0;--tw-shadow:0 24px 48px var(--tw-shadow-color,#00000073);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.2,.8,.2,1);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);will-change:opacity, transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.2,.8,.2,1);display:grid;position:absolute;scale:.96}.lang-dropdown.show{pointer-events:auto;--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y);--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y);opacity:1}.lang-dropdown-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2.5);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);text-align:left;--tw-font-weight:var(--font-weight-bold);font-size:13px;font-weight:var(--font-weight-bold);color:var(--text-secondary);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#0000;border-width:0;display:flex}@media (hover:hover){.lang-dropdown-item:hover{background-color:var(--bg-card-hover);color:var(--text-primary)}}.lang-dropdown-item+.lang-dropdown-item{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border)}.lang-dropdown-item.is-active{color:var(--accent)}.lang-dropdown-flag{--tw-leading:1;font-family:Twemoji Country Flags,Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif;font-size:16px;line-height:1}.lang-dropdown-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.user-chip{max-width:200px;min-height:38px;min-width:calc(var(--spacing) * 0);cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-block:calc(var(--spacing) * 1);padding-right:calc(var(--spacing) * 2.5);padding-left:calc(var(--spacing) * 1);text-align:left;--tw-font-weight:var(--font-weight-extrabold);font-size:13px;font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.user-chip:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.user-chip:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.user-chip:hover{background-color:var(--bg-card-hover)}}.user-chip[aria-expanded=true]{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.user-chip[aria-expanded=true]{border-color:color-mix(in srgb,var(--accent) 55%,var(--border))}}.user-chip[aria-expanded=true]{background-color:var(--bg-card-hover)}.user-chip-avatar{border-radius:var(--radius-sm);object-fit:cover;background:var(--accent);flex:none;width:28px;height:28px}@supports (color:color-mix(in lab, red, red)){.user-chip-avatar{background:color-mix(in srgb, var(--accent) 10%, var(--bg-card))}}.user-chip-avatar{image-rendering:pixelated}.user-chip-name{min-width:calc(var(--spacing) * 0);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.user-chip-caret{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);fill:var(--text-secondary);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));flex:none}.user-chip[aria-expanded=true] .user-chip-caret{transform:rotate(180deg)}.user-dropdown{pointer-events:none;top:calc(100% + 8px);right:calc(var(--spacing) * 0);z-index:30;transform-origin:100% 0;--tw-translate-y:calc(var(--spacing) * -2);width:min(100vw - 28px,280px);translate:var(--tw-translate-x) var(--tw-translate-y);gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);opacity:0;--tw-shadow:0 24px 48px var(--tw-shadow-color,#00000073);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.2,.8,.2,1);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);will-change:opacity, transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.2,.8,.2,1);display:grid;position:absolute;scale:.96}.user-dropdown.show{pointer-events:auto;--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y);--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y);opacity:1}.user-dropdown-head{align-items:center;gap:calc(var(--spacing) * 2.5);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);display:flex}.user-dropdown-avatar{border-radius:var(--radius-md);object-fit:cover;background:var(--accent);flex:none;width:40px;height:40px}@supports (color:color-mix(in lab, red, red)){.user-dropdown-avatar{background:color-mix(in srgb, var(--accent) 10%, var(--bg-card))}}.user-dropdown-avatar{image-rendering:pixelated}.user-dropdown-head-text{min-width:calc(var(--spacing) * 0);gap:calc(var(--spacing) * .5);display:grid}.user-dropdown-name{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);text-overflow:ellipsis;white-space:nowrap;color:var(--text-primary);overflow:hidden}.user-dropdown-sub{font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted);font-size:11px;overflow:hidden}.user-dropdown-rows{display:grid}.user-dropdown-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);display:flex}.user-dropdown-row+.user-dropdown-row{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border)}.user-dropdown-row-label{font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:11px;font-weight:var(--font-weight-bold);color:var(--text-muted)}.user-dropdown-row-status{text-align:right;--tw-font-weight:var(--font-weight-bold);font-size:13px;font-weight:var(--font-weight-bold);text-overflow:ellipsis;white-space:nowrap;color:var(--text-primary);overflow:hidden}.user-dropdown-row-status.linked{color:#6ee7b7}.user-dropdown-row-status.unlinked{color:var(--text-muted)}.user-dropdown-logout{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border);width:100%;min-height:42px;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-size:13px;font-weight:var(--font-weight-extrabold);color:var(--text-secondary);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#0000;border-radius:0;display:flex}@media (hover:hover){.user-dropdown-logout:hover{background-color:var(--bg-card-hover);color:var(--text-primary)}}.user-dropdown-logout svg{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);fill:currentColor}.sub-status{align-items:center;gap:calc(var(--spacing) * 2.5);min-height:28px;color:var(--danger);display:flex}.sub-status.active{color:var(--success)}.sub-status-dot{background:currentColor;border-radius:3.40282e38px;flex:none;width:10px;height:10px;display:inline-block;position:relative;box-shadow:0 0 12px}.sub-status-dot:after{content:"";opacity:.55;background:currentColor;border-radius:9999px;animation:1.8s cubic-bezier(.4,0,.6,1) infinite sub-status-pulse;position:absolute;inset:0}.sub-status-text{--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);letter-spacing:.01em}@keyframes brand-logo-spin{to{transform:rotate(360deg)}}.sub-countdown{gap:calc(var(--spacing) * 1);display:grid}.sub-countdown-row{align-items:baseline;column-gap:calc(var(--spacing) * 2);row-gap:calc(var(--spacing) * .5);flex-wrap:wrap;display:flex}.sub-countdown-value{--tw-leading:1.1;--tw-font-weight:var(--font-weight-extrabold);font-size:22px;line-height:1.1;font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.sub-countdown-caption{font-family:var(--font-mono);--tw-leading:1.3;color:var(--text-muted);font-size:12px;line-height:1.3}.traffic-bar{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background:#ffffff05;border-radius:3.40282e38px;justify-content:center;align-items:center;width:100%;min-height:24px;display:flex;position:relative;overflow:hidden}.traffic-bar-fill{inset-block:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);background:var(--accent);width:0%;position:absolute}@supports (color:color-mix(in lab, red, red)){.traffic-bar-fill{background:color-mix(in srgb, var(--accent) 28%, transparent)}}.traffic-bar-fill{transition:width .3s cubic-bezier(.4,0,.2,1),background .2s}.traffic-bar.over .traffic-bar-fill{background:var(--danger)}@supports (color:color-mix(in lab, red, red)){.traffic-bar.over .traffic-bar-fill{background:color-mix(in srgb, var(--danger) 32%, transparent)}}.traffic-bar.unlimited .traffic-bar-fill{background:var(--success);width:100%}@supports (color:color-mix(in lab, red, red)){.traffic-bar.unlimited .traffic-bar-fill{background:color-mix(in srgb, var(--success) 12%, transparent)}}.traffic-bar-value{z-index:1;min-width:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 3);text-align:center;font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:12px;font-weight:var(--font-weight-bold);white-space:nowrap;color:#fff;font-variant-numeric:tabular-nums;position:relative}.traffic-bar.unlimited .traffic-bar-value{animation:1.8s ease-in-out infinite traffic-infinity-pulse}@keyframes traffic-infinity-pulse{0%,to{opacity:1;text-shadow:0 0 0 color-mix(in srgb, var(--success) 0%, transparent)}50%{opacity:.7;text-shadow:0 0 10px color-mix(in srgb, var(--success) 70%, transparent)}}.link-cta-head{gap:calc(var(--spacing) * 1);display:grid}.link-cta-title{--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.link-cta-caption{--tw-leading:1.4;color:var(--text-secondary);font-size:13px;line-height:1.4}@keyframes sub-status-pulse{0%{opacity:.55;transform:scale(1)}70%{opacity:0;transform:scale(2.2)}to{opacity:0;transform:scale(2.2)}}.login-head{min-width:calc(var(--spacing) * 0);justify-content:center;align-items:center;gap:calc(var(--spacing) * 3.5);display:flex}.btn{min-height:46px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;padding-block:11px;padding-inline:13px;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.btn:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.btn:hover{background-color:var(--bg-card-hover)}}.btn{overflow-wrap:anywhere}.btn-primary{min-height:46px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--accent);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn-primary{border-color:color-mix(in srgb,var(--accent) 80%,var(--border))}}.btn-primary{background-color:var(--accent);text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:#04110a;--tw-shadow:0 10px 26px var(--tw-shadow-color,var(--accent));padding-block:11px;padding-inline:13px;text-decoration-line:none}@supports (color:color-mix(in lab, red, red)){.btn-primary{--tw-shadow:0 10px 26px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 18%,transparent))}}.btn-primary{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (hover:hover){.btn-primary:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);background-color:var(--accent-hover);--tw-shadow:0 14px 34px var(--tw-shadow-color,var(--accent))}@supports (color:color-mix(in lab, red, red)){.btn-primary:hover{--tw-shadow:0 14px 34px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 24%,transparent))}}.btn-primary:hover{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.btn-primary{overflow-wrap:anywhere}.btn-ghost{width:100%;min-height:46px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--accent);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn-ghost{border-color:color-mix(in srgb,var(--accent) 35%,var(--border))}}.btn-ghost{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn-ghost{background-color:color-mix(in srgb,var(--accent) 5%,transparent)}}.btn-ghost{text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--accent);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);padding-block:11px;padding-inline:13px;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (hover:hover){.btn-ghost:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn-ghost:hover{background-color:color-mix(in srgb,var(--accent) 10%,transparent)}}.btn-ghost:hover{--tw-shadow:0 10px 24px var(--tw-shadow-color,var(--accent))}@supports (color:color-mix(in lab, red, red)){.btn-ghost:hover{--tw-shadow:0 10px 24px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 10%,transparent))}}.btn-ghost:hover{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.btn-ghost{overflow-wrap:anywhere}.icon-btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:38px;height:38px;color:var(--text-secondary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;flex:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.icon-btn:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.icon-btn:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.icon-btn:hover{background-color:var(--bg-card-hover)}}.input{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:100%;min-height:46px;padding-inline:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--text-primary);transition-property:border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);--tw-outline-style:none;background-color:#05080d6b;outline-style:none;padding-block:11px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.input::placeholder{color:var(--text-muted)}.input:focus{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.input:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--border))}}.input:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,var(--accent)), 0 12px 28px var(--tw-shadow-color,#0003);background-color:#070c13ad}@supports (color:color-mix(in lab, red, red)){.input:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 12%,transparent)), 0 12px 28px var(--tw-shadow-color,#0003)}}.input:focus{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.input[aria-invalid=true]{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.input[aria-invalid=true]{border-color:color-mix(in srgb,var(--danger) 72%,var(--border))}}.input[aria-invalid=true]:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,var(--danger))}@supports (color:color-mix(in lab, red, red)){.input[aria-invalid=true]:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,color-mix(in srgb,var(--danger) 14%,transparent))}}.input[aria-invalid=true]:focus{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.input-code{text-align:center;font-family:var(--font-mono);--tw-tracking:0;letter-spacing:0}.panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);--tw-shadow:0 20px 40px var(--tw-shadow-color,#0000002e);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);position:relative;overflow:hidden}.panel-modal{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);--tw-shadow:0 30px 60px var(--tw-shadow-color,#00000047);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);position:relative;overflow:hidden}.step-box{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:58px;color:var(--text-muted);--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff0b);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);background-color:#ffffff05;padding:9px}.modal{pointer-events:none;inset:calc(var(--spacing) * 0);z-index:20;opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));padding:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra)) 14px max(var(--app-safe-bottom), 14px);place-items:center;display:grid;position:fixed}.modal.show{pointer-events:auto;opacity:1}.modal-backdrop,.tools-modal-backdrop{inset:calc(var(--spacing) * 0);border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);background:#03060ab8;border-width:0;position:absolute}.tools-modal-backdrop{z-index:25;position:fixed}.modal-card{width:min(100%,560px);max-height:min(86vh,760px);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.22s;--tw-ease:cubic-bezier(.2,.8,.2,1);transition-duration:.22s;transition-timing-function:cubic-bezier(.2,.8,.2,1);position:relative;overflow:auto;transform:translateY(18px)scale(.98)}.modal.show .modal-card{transform:translateY(0)scale(1)}.app{gap:calc(var(--spacing) * 4);width:min(100vw,560px);max-width:100vw;padding:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra)) 14px max(var(--app-safe-bottom), 22px);flex-direction:column;margin-inline:auto;display:flex}@media (min-width:561px){.app{justify-content:center;min-height:100vh}}.telegram-login-widget{min-width:calc(var(--spacing) * 0);align-items:center;display:flex}.brand-logo-shell{flex:none;place-items:center;width:38px;height:38px;display:grid;position:relative;overflow:hidden}.brand-logo-shell--lg{width:96px;height:96px}.brand-logo{inset:calc(var(--spacing) * 0);object-fit:contain;width:100%;height:100%;position:absolute}.brand-logo-spinner{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);border-style:var(--tw-border-style);border-width:2px;border-color:var(--accent);border-radius:3.40282e38px;flex:none}@supports (color:color-mix(in lab, red, red)){.brand-logo-spinner{border-color:color-mix(in srgb, var(--accent) 18%, var(--border))}}.brand-logo-spinner{border-top-color:var(--accent);animation:.85s linear infinite brand-logo-spin}.brand-logo--lg{width:84px;height:84px}.plans,.methods,.metrics,.referral-link-list,.bonus-list{min-width:calc(var(--spacing) * 0);display:grid}.actions,.otp-slots,.metric{min-width:calc(var(--spacing) * 0);gap:calc(var(--spacing) * 2);display:grid}.actions{grid-template-columns:minmax(0,1fr) 48px}.metric{grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr)}.metrics{gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background:#ffffff05;overflow:hidden}.metric,.notice,.otp-slots span{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background:#ffffff05}.metric{align-items:center;gap:calc(var(--spacing) * 2.5);min-height:46px;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);min-height:calc(var(--spacing) * 12)}.metric+.metric{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border)}.brand-title,.login-brand-title{max-width:260px;font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);--tw-tracking:0;letter-spacing:0;color:var(--accent)}.brand-title{--tw-leading:1.15;text-overflow:ellipsis;white-space:nowrap;text-shadow:0 0 20px var(--accent);font-size:17px;line-height:1.15;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.brand-title{text-shadow:0 0 20px color-mix(in srgb, var(--accent) 18%, transparent)}}.login-brand-title{text-align:left;font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-leading:1.1;overflow-wrap:anywhere;line-height:1.1}.login-auth-body{align-content:flex-start;gap:calc(var(--spacing) * 2.5);width:100%;min-height:140px;display:grid}.login-auth-body--telegram{align-content:center}.login-text,.promo-status{color:var(--text-secondary)}.section-label{min-width:calc(var(--spacing) * 0);font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-size:11px;font-weight:var(--font-weight-bold);color:var(--text-muted);overflow-wrap:anywhere;font-weight:var(--font-weight-extrabold)}.mono-pill,.badge{min-height:calc(var(--spacing) * 7);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:11px;font-weight:var(--font-weight-bold);white-space:nowrap;align-items:center;display:inline-flex;box-shadow:inset 0 1px #ffffff0f}.mono-pill{color:var(--accent);border:1px solid var(--accent)}@supports (color:color-mix(in lab, red, red)){.mono-pill{border:1px solid color-mix(in srgb, var(--accent) 45%, var(--border))}}.mono-pill{background:var(--accent)}@supports (color:color-mix(in lab, red, red)){.mono-pill{background:color-mix(in srgb, var(--accent) 9%, transparent)}}.badge{min-width:calc(var(--spacing) * 0);text-overflow:ellipsis;color:#6ee7b7;border:1px solid var(--success);overflow:hidden}@supports (color:color-mix(in lab, red, red)){.badge{border:1px solid color-mix(in srgb, var(--success) 34%, var(--border))}}.badge{background:#10b9811f}.badge.off,.promo-status.error,.login-status.error{color:#fca5a5}.badge.off{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.badge.off{border-color:color-mix(in srgb, var(--danger) 34%, var(--border))}}.badge.off{background:#ef44441f}.promo-status.success{color:#6ee7b7}#legal-links-login{width:min(100vw,560px)}.legal-link,.code-modal-resend{border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);--tw-font-weight:var(--font-weight-bold);font-size:13px;font-weight:var(--font-weight-bold);color:var(--text-secondary);text-underline-offset:3px;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));text-decoration-line:underline;-webkit-text-decoration-color:var(--accent);-webkit-text-decoration-color:var(--accent);text-decoration-color:var(--accent);background-color:#0000;border-width:0}@supports (color:color-mix(in lab, red, red)){.legal-link,.code-modal-resend{-webkit-text-decoration-color:color-mix(in srgb, var(--accent) 42%, currentColor);-webkit-text-decoration-color:color-mix(in srgb, var(--accent) 42%, currentColor);text-decoration-color:color-mix(in srgb, var(--accent) 42%, currentColor)}}.legal-link{flex:0 auto;display:inline}.otp-input{inset:calc(var(--spacing) * 0);z-index:2;border-style:var(--tw-border-style);color:#0000;opacity:.01;--tw-outline-style:none;caret-color:#0000;background-color:#0000;border-width:0;outline-style:none;width:100%;height:100%;position:absolute}.otp-slots{grid-template-columns:repeat(6,minmax(0,1fr))}.otp-slots span{aspect-ratio:1;min-width:calc(var(--spacing) * 0);border-radius:var(--radius-md);font-family:var(--font-mono);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-leading:1;--tw-font-weight:850;color:var(--text-primary);transition-property:border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background:#05080d6b;place-items:center;font-weight:850;line-height:1;display:grid}.otp-slots span.filled{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.filled{border-color:color-mix(in srgb, var(--accent) 35%, var(--border))}}.otp-slots span.filled{background:var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.filled{background:color-mix(in srgb, var(--accent) 7%, #ffffff09)}}.otp-slots span.active{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.active{border-color:color-mix(in srgb, var(--accent) 72%, var(--border))}}.otp-slots span.active{box-shadow:0 0 0 3px var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.active{box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent)}}.step-num,.step-name{color:currentColor;display:block}.step-num{font-family:var(--font-mono);--tw-leading:1.1;--tw-font-weight:var(--font-weight-extrabold);font-size:11px;line-height:1.1;font-weight:var(--font-weight-extrabold)}.step-name{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1.15;--tw-font-weight:var(--font-weight-extrabold);line-height:1.15;font-weight:var(--font-weight-extrabold)}.step.active,.auth-tab.active{border-color:var(--accent);color:var(--accent);background:var(--accent)}@supports (color:color-mix(in lab, red, red)){.step.active,.auth-tab.active{background:color-mix(in srgb, var(--accent) 9%, transparent)}}.step.done{color:var(--text-secondary);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.step.done{border-color:color-mix(in srgb, var(--accent) 45%, var(--border))}}.notice{border-radius:var(--radius-md);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);padding:13px;line-height:1.45}.login-layout.show{display:flex}.telegram-login-widget{min-height:calc(var(--spacing) * 14);justify-content:center}.login-text,.promo-status{margin:calc(var(--spacing) * 0);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;line-height:1.45}.promo-status{margin-top:calc(var(--spacing) * .5);overflow-wrap:anywhere;min-height:22px}.login-status{margin:calc(var(--spacing) * 0);min-height:calc(var(--spacing) * 5)}.toast{z-index:40;border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-bold);width:max-content;max-width:calc(100vw - 28px);font-size:13px;font-weight:var(--font-weight-bold);color:var(--text-primary);opacity:0;transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:cubic-bezier(.2,.8,.2,1);left:50%;bottom:max(var(--app-safe-bottom), 22px);border:1px solid var(--accent);padding-block:11px;padding-inline:13px;transition-duration:.3s;transition-timing-function:cubic-bezier(.2,.8,.2,1);position:fixed}@supports (color:color-mix(in lab, red, red)){.toast{border:1px solid color-mix(in srgb, var(--accent) 45%, var(--border))}}.toast{-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);box-shadow:0 16px 38px #00000073, 0 0 15px var(--accent);background:#0f1521d1}@supports (color:color-mix(in lab, red, red)){.toast{box-shadow:0 16px 38px #00000073, 0 0 15px color-mix(in srgb, var(--accent) 15%, transparent)}}.toast{pointer-events:none;transform:translate(-50%,14px)scale(.94)}.toast.show{opacity:1;transform:translate(-50%)scale(1)}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.z-\[1\]{z-index:1}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-\[3px\]{margin-top:3px}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-5{height:calc(var(--spacing) * 5)}.min-h-5{min-height:calc(var(--spacing) * 5)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-14{min-height:calc(var(--spacing) * 14)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-\[58px\]{min-height:58px}.w-5{width:calc(var(--spacing) * 5)}.w-12{width:calc(var(--spacing) * 12)}.w-\[min\(100\%\,420px\)\]{width:min(100%,420px)}.w-full{width:100%}.max-w-\[48\%\]{max-width:48%}.max-w-\[320px\]{max-width:320px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-12{min-width:calc(var(--spacing) * 12)}.flex-none{flex:none}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-text{cursor:text}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[minmax\(0\,0\.6fr\)_minmax\(0\,1\.4fr\)\]{grid-template-columns:minmax(0,.6fr) minmax(0,1.4fr)}.grid-cols-\[minmax\(0\,0\.8fr\)_minmax\(0\,1\.2fr\)\]{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr)}.grid-cols-\[minmax\(0\,1fr\)_48px\]{grid-template-columns:minmax(0,1fr) 48px}.grid-cols-\[minmax\(0\,1fr\)_minmax\(120px\,auto\)\]{grid-template-columns:minmax(0,1fr) minmax(120px,auto)}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-items-center{justify-items:center}.justify-items-stretch{justify-items:stretch}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-\[17px\]{gap:17px}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.rounded-\[var\(--radius-md\)\]{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.bg-\[color-mix\(in_srgb\,var\(--accent\)_8\%\,transparent\)\]{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-\[color-mix\(in_srgb\,var\(--accent\)_8\%\,transparent\)\]{background-color:color-mix(in srgb,var(--accent) 8%,transparent)}}.bg-\[rgba\(255\,255\,255\,0\.02\)\]{background-color:#ffffff05}.bg-\[rgba\(255\,255\,255\,0\.03\)\]{background-color:#ffffff08}.bg-\[rgba\(255\,255\,255\,0\.015\)\]{background-color:#ffffff04}.fill-current{fill:currentColor}.p-0{padding:calc(var(--spacing) * 0)}.p-3\.5{padding:calc(var(--spacing) * 3.5)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-\[13px\]{padding:13px}.p-\[17px\]{padding:17px}.p-\[18px\]{padding:18px}.px-0{padding-inline:calc(var(--spacing) * 0)}.px-3{padding-inline:calc(var(--spacing) * 3)}.pt-3\.5{padding-top:calc(var(--spacing) * 3.5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-\[family-name\:var\(--font-mono\)\]{font-family:var(--font-mono)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[20px\]{font-size:20px}.text-\[22px\]{font-size:22px}.text-\[28px\]{font-size:28px}.text-\[32px\]{font-size:32px}.leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}.leading-\[1\.3\]{--tw-leading:1.3;line-height:1.3}.leading-\[1\.05\]{--tw-leading:1.05;line-height:1.05}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-\[1\.08\]{--tw-leading:1.08;line-height:1.08}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[1\.35\]{--tw-leading:1.35;line-height:1.35}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.tracking-\[0\]{--tw-tracking:0;letter-spacing:0}.break-words{overflow-wrap:break-word}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-current{color:currentColor}.uppercase{text-transform:uppercase}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-\[color-mix\(in_srgb\,var\(--accent\)_58\%\,transparent\)\]{--tw-ring-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.ring-\[color-mix\(in_srgb\,var\(--accent\)_58\%\,transparent\)\]{--tw-ring-color:color-mix(in srgb,var(--accent) 58%,transparent)}}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-\[transform\,border-color\,background\,box-shadow\]{transition-property:transform,border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.hover\:-translate-y-0:hover{--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:border-\[color-mix\(in_srgb\,var\(--accent\)_42\%\,var\(--border\)\)\]:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:border-\[color-mix\(in_srgb\,var\(--accent\)_42\%\,var\(--border\)\)\]:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}}@media not all and (min-width:560px){.max-\[560px\]\:flex-\[1_1_100\%\]{flex:100%}}@media not all and (min-width:460px){.max-\[460px\]\:max-w-\[240px\]{max-width:240px}.max-\[460px\]\:text-\[24px\]{font-size:24px}}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}:root{color-scheme:dark;--bg-primary:#05070a;--bg-card:#0f1521;--bg-card-hover:#161e2e;--border:#1e2940;--text-primary:#e2e8f0;--text-secondary:#94a3b8;--text-muted:#64748b;--accent:#00fe7a;--accent-hover:#5aff9f;--success:#10b981;--danger:#ef4444;--font-sans:Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:"JetBrains Mono", "Fira Code", monospace;--radius-sm:6px;--radius-md:8px;--radius-lg:8px;--transition:.2s cubic-bezier(.4, 0, .2, 1);--app-safe-top-extra:10px;--app-safe-top:max(var(--tg-content-safe-area-inset-top,0px), var(--tg-safe-area-inset-top,0px), env(safe-area-inset-top));--app-safe-bottom:max(var(--tg-content-safe-area-inset-bottom,0px), var(--tg-safe-area-inset-bottom,0px), env(safe-area-inset-bottom))}.hidden{display:none!important}@supports not (color:color-mix(in srgb, #000 50%, #fff)){.mono-pill,.step.active,.auth-tab.active{border-color:var(--accent);background:#00fe7a14}}@media (hover:hover) and (pointer:fine){.legal-link:hover:not(:disabled),.code-modal-resend:hover:not(:disabled){color:var(--accent)}}@media (max-width:460px){.app{padding-inline:calc(var(--spacing) * 2.5)}.user-chip{max-width:150px}.user-chip-name{display:none}.brand-title{max-width:164px}.metric{grid-template-columns:repeat(1,minmax(0,1fr))}.badge,.mono-pill{max-width:100%}.otp-slots{gap:calc(var(--spacing) * 1.5)}.otp-slots span{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.brand-logo--lg{width:68px;height:68px}.brand-logo-shell--lg{width:96px;height:96px}.login-auth-body{min-height:128px}.login-brand-title{max-width:200px;font-size:21px}}@media (max-width:380px){.brand-title{max-width:150px}}@media (max-width:560px){.step-box{min-height:54px;padding-inline:7px;padding-block:calc(var(--spacing) * 2)}.modal{padding:calc(var(--spacing) * 0);padding:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra)) 14px max(var(--app-safe-bottom), 14px);place-items:center;top:0;bottom:0}.modal--page{padding:calc(var(--spacing) * 0);place-items:stretch stretch;padding:0!important}.modal--page .modal-backdrop{background-color:var(--bg-primary);opacity:1;-webkit-backdrop-filter:none;backdrop-filter:none}.modal-card{border-radius:var(--radius-lg);width:100%;height:auto;max-height:min(92vh,760px);transform:translateY(12px)scale(.98)}.modal--page .modal-card{border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;width:100%;max-width:none;height:100%;max-height:none;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-width:0;border-radius:0;transform:none!important}.modal.show .modal-card{transform:translateY(0)scale(1)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-bold:700;--font-weight-extrabold:800;--tracking-wide:.025em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html,body{margin:calc(var(--spacing) * 0);--tw-tracking:0;letter-spacing:0;width:100%;min-height:100%;overflow-x:hidden}body{background-color:var(--bg-primary);font-family:var(--font-sans);color:var(--text-primary);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-tap-highlight-color:transparent;overflow-x:hidden}body.modal-open{overflow:hidden}button,a{font:inherit}button{cursor:pointer;border-style:var(--tw-border-style);border-width:0}button:disabled{cursor:progress;opacity:.58}}@layer components{.btn-sm{min-height:38px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:11px;padding-block:calc(var(--spacing) * 2);text-align:center;--tw-leading:1;--tw-font-weight:var(--font-weight-extrabold);font-size:13px;line-height:1;font-weight:var(--font-weight-extrabold);color:var(--text-secondary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.btn-sm:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn-sm:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.btn-sm:hover{background-color:var(--bg-card-hover)}}.metrics{gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:#ffffff05;display:grid;overflow:hidden}.metric{min-height:calc(var(--spacing) * 12);align-items:center;gap:calc(var(--spacing) * 2.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);background-color:#ffffff05;grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr);display:grid}.auth-tab{min-height:calc(var(--spacing) * 9);border-radius:var(--radius-sm);--tw-font-weight:850;color:var(--text-secondary);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#0000;font-weight:850}.login-layout{justify-content:center;align-items:center;gap:calc(var(--spacing) * 4);width:min(100vw,560px);min-height:100vh;padding-inline:14px;padding-top:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra));padding-bottom:max(var(--app-safe-bottom), 18px);flex-direction:column;margin-inline:auto;display:none}.section-label{font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-size:11px;font-weight:var(--font-weight-extrabold);color:var(--text-muted)}.app-loader{min-height:78vh;font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-muted);place-items:center;display:grid}.auth-tabs-wrapper{gap:calc(var(--spacing) * 1);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:46px;padding:calc(var(--spacing) * 1);background-color:#05080d61;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.panel-head{justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);display:flex}.section-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-leading:var(--leading-tight);line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.section-caption{color:var(--text-secondary);margin-top:3px;font-size:13px}.metric-label{min-width:calc(var(--spacing) * 0);font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:11px;font-weight:var(--font-weight-bold);color:var(--text-muted)}.metric-value{min-width:calc(var(--spacing) * 0);text-align:right;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--text-primary)}.referral-link-row{align-items:center;gap:calc(var(--spacing) * 2.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:58px;padding-inline:calc(var(--spacing) * 3);background-color:#ffffff05;grid-template-columns:minmax(0,1fr) 48px;padding-block:11px;display:grid}.referral-link-value{margin-top:calc(var(--spacing) * 1);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1.35;--tw-font-weight:var(--font-weight-bold);line-height:1.35;font-weight:var(--font-weight-bold);color:var(--text-primary)}.bonus-row{align-items:center;gap:calc(var(--spacing) * 2.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:58px;padding-inline:calc(var(--spacing) * 3);background-color:#ffffff05;grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);padding-block:11px;display:grid}.empty-state{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);background-color:#ffffff05;padding:13px;line-height:1.45}.plan-card{width:100%;min-height:44px;min-width:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;--tw-leading:var(--leading-tight);font-size:14px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#ffffff05;justify-content:center;align-items:center;display:inline-flex}@media (hover:hover){.plan-card:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.plan-card:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.plan-card:hover{background-color:var(--bg-card-hover)}}.plan-card{transition-duration:.2s}.plan-card-active{border-color:var(--accent);background-color:color-mix(in srgb,var(--accent) 8%,transparent);color:var(--accent);--tw-shadow:0 12px 30px var(--tw-shadow-color,#0003);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:color-mix(in srgb,var(--accent) 58%,transparent)}.payment-method-card{width:100%;min-height:52px;min-width:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 4);text-align:center;--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#ffffff08;justify-content:center;align-items:center;padding-block:12px;display:inline-flex}@media (hover:hover){.payment-method-card:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.payment-method-card:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.payment-method-card:hover{background-color:var(--bg-card-hover)}}.payment-method-card{transition-duration:.2s}.payment-method-card--platega{background:#00fe7a14;border-color:#00fe7a5c}.payment-method-card--cryptopay{background:#22d3ee14;border-color:#22d3ee5c}.plan-name{--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);display:block}.plan-meta{margin-top:calc(var(--spacing) * 1);font-family:var(--font-mono);--tw-leading:1.3;--tw-font-weight:var(--font-weight-bold);font-size:11px;line-height:1.3;font-weight:var(--font-weight-bold);color:var(--text-muted);display:block}.plan-price{text-align:right;max-width:48%;font-family:var(--font-mono);--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--accent);flex:none;display:block}.notice{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);background-color:#ffffff05;padding:13px;line-height:1.45}.step-num{font-family:var(--font-mono);--tw-leading:1.1;--tw-font-weight:var(--font-weight-extrabold);font-size:11px;line-height:1.1;font-weight:var(--font-weight-extrabold);color:currentColor;display:block}.step-name{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1.15;--tw-font-weight:var(--font-weight-extrabold);line-height:1.15;font-weight:var(--font-weight-extrabold);color:currentColor;display:block}.main-value{margin:calc(var(--spacing) * 0);--tw-leading:1.08;--tw-font-weight:var(--font-weight-extrabold);font-size:32px;line-height:1.08;font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.main-caption{margin-top:calc(var(--spacing) * 1.5);margin-bottom:calc(var(--spacing) * 0);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);line-height:1.45}.status-text{margin:calc(var(--spacing) * 0);min-height:calc(var(--spacing) * 5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);line-height:1.45}.legal-links{justify-content:center;column-gap:calc(var(--spacing) * 4);row-gap:calc(var(--spacing) * 1.5);text-align:center;flex-wrap:wrap;display:flex}.app-header{min-height:calc(var(--spacing) * 12);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.app-header-title{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2.5);display:flex}.app-header-actions{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.panel-head-sm{min-width:calc(var(--spacing) * 0);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2.5);display:flex}.user-menu,.lang-menu{display:inline-flex;position:relative}.lang-chip{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:38px;padding-inline:calc(var(--spacing) * 2);text-align:left;--tw-font-weight:var(--font-weight-extrabold);font-size:12px;font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.lang-chip:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.lang-chip:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.lang-chip:hover{background-color:var(--bg-card-hover)}}.lang-chip[aria-expanded=true]{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.lang-chip[aria-expanded=true]{border-color:color-mix(in srgb,var(--accent) 55%,var(--border))}}.lang-chip[aria-expanded=true]{background-color:var(--bg-card-hover)}.lang-chip-flag{--tw-leading:1;font-family:Twemoji Country Flags,Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif;font-size:14px;line-height:1}.lang-chip-label{font-family:var(--font-mono);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);font-size:11px}.lang-chip-caret{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);fill:var(--text-secondary);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));flex:none}.lang-chip[aria-expanded=true] .lang-chip-caret{transform:rotate(180deg)}.lang-dropdown{pointer-events:none;top:calc(100% + 8px);right:calc(var(--spacing) * 0);z-index:30;transform-origin:100% 0;--tw-translate-y:calc(var(--spacing) * -2);width:160px;translate:var(--tw-translate-x) var(--tw-translate-y);gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);opacity:0;--tw-shadow:0 24px 48px var(--tw-shadow-color,#00000073);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.2,.8,.2,1);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);will-change:opacity, transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.2,.8,.2,1);display:grid;position:absolute;scale:.96}.lang-dropdown.show{pointer-events:auto;--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y);--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y);opacity:1}.lang-dropdown-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2.5);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);text-align:left;--tw-font-weight:var(--font-weight-bold);font-size:13px;font-weight:var(--font-weight-bold);color:var(--text-secondary);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#0000;border-width:0;display:flex}@media (hover:hover){.lang-dropdown-item:hover{background-color:var(--bg-card-hover);color:var(--text-primary)}}.lang-dropdown-item+.lang-dropdown-item{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border)}.lang-dropdown-item.is-active{background-color:var(--bg-card-hover);color:var(--text-primary)}.lang-dropdown-flag{--tw-leading:1;font-family:Twemoji Country Flags,Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif;font-size:16px;line-height:1}.lang-dropdown-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.user-chip{max-width:200px;min-height:38px;min-width:calc(var(--spacing) * 0);cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);padding-block:calc(var(--spacing) * 1);padding-right:calc(var(--spacing) * 2.5);padding-left:calc(var(--spacing) * 1);text-align:left;--tw-font-weight:var(--font-weight-extrabold);font-size:13px;font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.user-chip:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.user-chip:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.user-chip:hover{background-color:var(--bg-card-hover)}}.user-chip[aria-expanded=true]{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.user-chip[aria-expanded=true]{border-color:color-mix(in srgb,var(--accent) 55%,var(--border))}}.user-chip[aria-expanded=true]{background-color:var(--bg-card-hover)}.user-chip-avatar{border-radius:var(--radius-sm);object-fit:cover;background:var(--accent);flex:none;width:28px;height:28px}@supports (color:color-mix(in lab, red, red)){.user-chip-avatar{background:color-mix(in srgb, var(--accent) 10%, var(--bg-card))}}.user-chip-avatar{image-rendering:pixelated}.user-chip-name{min-width:calc(var(--spacing) * 0);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.user-chip-caret{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);fill:var(--text-secondary);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));flex:none}.user-chip[aria-expanded=true] .user-chip-caret{transform:rotate(180deg)}.user-dropdown{pointer-events:none;top:calc(100% + 8px);right:calc(var(--spacing) * 0);z-index:30;transform-origin:100% 0;--tw-translate-y:calc(var(--spacing) * -2);width:min(100vw - 28px,280px);translate:var(--tw-translate-x) var(--tw-translate-y);gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);opacity:0;--tw-shadow:0 24px 48px var(--tw-shadow-color,#00000073);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.2,.8,.2,1);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);will-change:opacity, transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.2,.8,.2,1);display:grid;position:absolute;scale:.96}.user-dropdown.show{pointer-events:auto;--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y);--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y);opacity:1}.user-dropdown-head{align-items:center;gap:calc(var(--spacing) * 2.5);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);display:flex}.user-dropdown-avatar{border-radius:var(--radius-md);object-fit:cover;background:var(--accent);flex:none;width:40px;height:40px}@supports (color:color-mix(in lab, red, red)){.user-dropdown-avatar{background:color-mix(in srgb, var(--accent) 10%, var(--bg-card))}}.user-dropdown-avatar{image-rendering:pixelated}.user-dropdown-head-text{min-width:calc(var(--spacing) * 0);gap:calc(var(--spacing) * .5);display:grid}.user-dropdown-name{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);text-overflow:ellipsis;white-space:nowrap;color:var(--text-primary);overflow:hidden}.user-dropdown-sub{font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted);font-size:11px;overflow:hidden}.user-dropdown-rows{display:grid}.user-dropdown-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);display:flex}.user-dropdown-row+.user-dropdown-row{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border)}.user-dropdown-row-label{font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:11px;font-weight:var(--font-weight-bold);color:var(--text-muted)}.user-dropdown-row-status{text-align:right;--tw-font-weight:var(--font-weight-bold);font-size:13px;font-weight:var(--font-weight-bold);text-overflow:ellipsis;white-space:nowrap;color:var(--text-primary);overflow:hidden}.user-dropdown-row-status.linked{color:#6ee7b7}.user-dropdown-row-status.unlinked{color:var(--text-muted)}.user-dropdown-logout{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border);width:100%;min-height:42px;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-size:13px;font-weight:var(--font-weight-extrabold);color:var(--text-secondary);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:#0000;border-radius:0;display:flex}@media (hover:hover){.user-dropdown-logout:hover{background-color:var(--bg-card-hover);color:var(--text-primary)}}.user-dropdown-logout svg{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);fill:currentColor}.sub-status{align-items:center;gap:calc(var(--spacing) * 2.5);min-height:28px;color:var(--danger);display:flex}.sub-status.active{color:var(--success)}.sub-status-dot{background:currentColor;border-radius:3.40282e38px;flex:none;width:10px;height:10px;display:inline-block;position:relative;box-shadow:0 0 12px}.sub-status-dot:after{content:"";opacity:.55;background:currentColor;border-radius:9999px;animation:1.8s cubic-bezier(.4,0,.6,1) infinite sub-status-pulse;position:absolute;inset:0}.sub-status-text{--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);letter-spacing:.01em}@keyframes brand-logo-spin{to{transform:rotate(360deg)}}.sub-countdown{gap:calc(var(--spacing) * 1);display:grid}.sub-countdown-row{align-items:baseline;column-gap:calc(var(--spacing) * 2);row-gap:calc(var(--spacing) * .5);flex-wrap:wrap;display:flex}.sub-countdown-value{--tw-leading:1.1;--tw-font-weight:var(--font-weight-extrabold);font-size:22px;line-height:1.1;font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.sub-countdown-caption{font-family:var(--font-mono);--tw-leading:1.3;color:var(--text-muted);font-size:12px;line-height:1.3}.traffic-bar{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background:#ffffff05;border-radius:3.40282e38px;justify-content:center;align-items:center;width:100%;min-height:24px;display:flex;position:relative;overflow:hidden}.traffic-bar-fill{inset-block:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);background:var(--accent);width:0%;position:absolute}@supports (color:color-mix(in lab, red, red)){.traffic-bar-fill{background:color-mix(in srgb, var(--accent) 28%, transparent)}}.traffic-bar-fill{transition:width .3s cubic-bezier(.4,0,.2,1),background .2s}.traffic-bar.over .traffic-bar-fill{background:var(--danger)}@supports (color:color-mix(in lab, red, red)){.traffic-bar.over .traffic-bar-fill{background:color-mix(in srgb, var(--danger) 32%, transparent)}}.traffic-bar.unlimited .traffic-bar-fill{background:var(--success);width:100%}@supports (color:color-mix(in lab, red, red)){.traffic-bar.unlimited .traffic-bar-fill{background:color-mix(in srgb, var(--success) 12%, transparent)}}.traffic-bar-value{z-index:1;min-width:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 3);text-align:center;font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:12px;font-weight:var(--font-weight-bold);white-space:nowrap;color:#fff;font-variant-numeric:tabular-nums;position:relative}.traffic-bar.unlimited .traffic-bar-value{animation:1.8s ease-in-out infinite traffic-infinity-pulse}@keyframes traffic-infinity-pulse{0%,to{opacity:1;text-shadow:0 0 0 color-mix(in srgb, var(--success) 0%, transparent)}50%{opacity:.7;text-shadow:0 0 10px color-mix(in srgb, var(--success) 70%, transparent)}}.link-cta-head{gap:calc(var(--spacing) * 1);display:grid}.link-cta-title{--tw-leading:var(--leading-tight);font-size:15px;line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary)}.link-cta-caption{--tw-leading:1.4;color:var(--text-secondary);font-size:13px;line-height:1.4}@keyframes sub-status-pulse{0%{opacity:.55;transform:scale(1)}70%{opacity:0;transform:scale(2.2)}to{opacity:0;transform:scale(2.2)}}.login-head{min-width:calc(var(--spacing) * 0);justify-content:center;align-items:center;gap:calc(var(--spacing) * 3.5);display:flex}.btn{min-height:46px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--text-primary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;padding-block:11px;padding-inline:13px;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.btn:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.btn:hover{background-color:var(--bg-card-hover)}}.btn{overflow-wrap:anywhere}.btn-primary{min-height:46px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--accent);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn-primary{border-color:color-mix(in srgb,var(--accent) 80%,var(--border))}}.btn-primary{background-color:var(--accent);text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:#04110a;--tw-shadow:0 10px 26px var(--tw-shadow-color,var(--accent));padding-block:11px;padding-inline:13px;text-decoration-line:none}@supports (color:color-mix(in lab, red, red)){.btn-primary{--tw-shadow:0 10px 26px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 18%,transparent))}}.btn-primary{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (hover:hover){.btn-primary:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);background-color:var(--accent-hover);--tw-shadow:0 14px 34px var(--tw-shadow-color,var(--accent))}@supports (color:color-mix(in lab, red, red)){.btn-primary:hover{--tw-shadow:0 14px 34px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 24%,transparent))}}.btn-primary:hover{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.btn-primary{overflow-wrap:anywhere}.btn-ghost{width:100%;min-height:46px;min-width:calc(var(--spacing) * 0);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--accent);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn-ghost{border-color:color-mix(in srgb,var(--accent) 35%,var(--border))}}.btn-ghost{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn-ghost{background-color:color-mix(in srgb,var(--accent) 5%,transparent)}}.btn-ghost{text-align:center;--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);color:var(--accent);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);padding-block:11px;padding-inline:13px;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (hover:hover){.btn-ghost:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.btn-ghost:hover{background-color:color-mix(in srgb,var(--accent) 10%,transparent)}}.btn-ghost:hover{--tw-shadow:0 10px 24px var(--tw-shadow-color,var(--accent))}@supports (color:color-mix(in lab, red, red)){.btn-ghost:hover{--tw-shadow:0 10px 24px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 10%,transparent))}}.btn-ghost:hover{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.btn-ghost{overflow-wrap:anywhere}.icon-btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:38px;height:38px;color:var(--text-secondary);transition-property:transform,border-color,background,color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);background-color:#ffffff08;flex:none;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}@media (hover:hover){.icon-btn:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.icon-btn:hover{border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}}.icon-btn:hover{background-color:var(--bg-card-hover)}}.input{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:100%;min-height:46px;padding-inline:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--text-primary);transition-property:border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:cubic-bezier(.4,0,.2,1);--tw-outline-style:none;background-color:#05080d6b;outline-style:none;padding-block:11px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.input::placeholder{color:var(--text-muted)}.input:focus{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.input:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--border))}}.input:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,var(--accent)), 0 12px 28px var(--tw-shadow-color,#0003);background-color:#070c13ad}@supports (color:color-mix(in lab, red, red)){.input:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,color-mix(in srgb,var(--accent) 12%,transparent)), 0 12px 28px var(--tw-shadow-color,#0003)}}.input:focus{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.input[aria-invalid=true]{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.input[aria-invalid=true]{border-color:color-mix(in srgb,var(--danger) 72%,var(--border))}}.input[aria-invalid=true]:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,var(--danger))}@supports (color:color-mix(in lab, red, red)){.input[aria-invalid=true]:focus{--tw-shadow:0 0 0 3px var(--tw-shadow-color,color-mix(in srgb,var(--danger) 14%,transparent))}}.input[aria-invalid=true]:focus{box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.input-code{text-align:center;font-family:var(--font-mono);--tw-tracking:0;letter-spacing:0}.panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);--tw-shadow:0 20px 40px var(--tw-shadow-color,#0000002e);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);position:relative;overflow:hidden}.panel-modal{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--bg-card);--tw-shadow:0 30px 60px var(--tw-shadow-color,#00000047);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);position:relative;overflow:hidden}.step-box{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);min-height:58px;color:var(--text-muted);--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff0b);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);background-color:#ffffff05;padding:9px}.modal{pointer-events:none;inset:calc(var(--spacing) * 0);z-index:20;opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));padding:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra)) 14px max(var(--app-safe-bottom), 14px);place-items:center;display:grid;position:fixed}.modal.show{pointer-events:auto;opacity:1}.modal-backdrop,.tools-modal-backdrop{inset:calc(var(--spacing) * 0);border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);background:#03060ab8;border-width:0;position:absolute}.tools-modal-backdrop{z-index:25;position:fixed}.modal-card{width:min(100%,560px);max-height:min(86vh,760px);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.22s;--tw-ease:cubic-bezier(.2,.8,.2,1);transition-duration:.22s;transition-timing-function:cubic-bezier(.2,.8,.2,1);position:relative;overflow:auto;transform:translateY(18px)scale(.98)}.modal.show .modal-card{transform:translateY(0)scale(1)}.app{gap:calc(var(--spacing) * 4);width:min(100vw,560px);max-width:100vw;padding:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra)) 14px max(var(--app-safe-bottom), 22px);flex-direction:column;margin-inline:auto;display:flex}@media (min-width:561px){.app{justify-content:center;min-height:100vh}}.telegram-login-widget{min-width:calc(var(--spacing) * 0);align-items:center;display:flex}.brand-logo-shell{flex:none;place-items:center;width:38px;height:38px;display:grid;position:relative;overflow:hidden}.brand-logo-shell--lg{width:96px;height:96px}.brand-logo{inset:calc(var(--spacing) * 0);object-fit:contain;width:100%;height:100%;position:absolute}.brand-logo-spinner{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);border-style:var(--tw-border-style);border-width:2px;border-color:var(--accent);border-radius:3.40282e38px;flex:none}@supports (color:color-mix(in lab, red, red)){.brand-logo-spinner{border-color:color-mix(in srgb, var(--accent) 18%, var(--border))}}.brand-logo-spinner{border-top-color:var(--accent);animation:.85s linear infinite brand-logo-spin}.brand-logo--lg{width:84px;height:84px}.plans,.methods,.metrics,.referral-link-list,.bonus-list{min-width:calc(var(--spacing) * 0);display:grid}.actions,.otp-slots,.metric{min-width:calc(var(--spacing) * 0);gap:calc(var(--spacing) * 2);display:grid}.actions{grid-template-columns:minmax(0,1fr) 48px}.metric{grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr)}.metrics{gap:calc(var(--spacing) * 0);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background:#ffffff05;overflow:hidden}.metric,.notice,.otp-slots span{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background:#ffffff05}.metric{align-items:center;gap:calc(var(--spacing) * 2.5);min-height:46px;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);min-height:calc(var(--spacing) * 12)}.metric+.metric{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border)}.brand-title,.login-brand-title{max-width:260px;font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);--tw-tracking:0;letter-spacing:0;color:var(--accent)}.brand-title{--tw-leading:1.15;text-overflow:ellipsis;white-space:nowrap;text-shadow:0 0 20px var(--accent);font-size:17px;line-height:1.15;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.brand-title{text-shadow:0 0 20px color-mix(in srgb, var(--accent) 18%, transparent)}}.login-brand-title{text-align:left;font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-leading:1.1;overflow-wrap:anywhere;line-height:1.1}.login-auth-body{align-content:flex-start;gap:calc(var(--spacing) * 2.5);width:100%;min-height:140px;display:grid}.login-auth-body--telegram{align-content:center}.login-text,.promo-status{color:var(--text-secondary)}.section-label{min-width:calc(var(--spacing) * 0);font-family:var(--font-mono);--tw-font-weight:var(--font-weight-extrabold);font-size:11px;font-weight:var(--font-weight-bold);color:var(--text-muted);overflow-wrap:anywhere;font-weight:var(--font-weight-extrabold)}.mono-pill,.badge{min-height:calc(var(--spacing) * 7);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);font-family:var(--font-mono);--tw-font-weight:var(--font-weight-bold);font-size:11px;font-weight:var(--font-weight-bold);white-space:nowrap;align-items:center;display:inline-flex;box-shadow:inset 0 1px #ffffff0f}.mono-pill{color:var(--accent);border:1px solid var(--accent)}@supports (color:color-mix(in lab, red, red)){.mono-pill{border:1px solid color-mix(in srgb, var(--accent) 45%, var(--border))}}.mono-pill{background:var(--accent)}@supports (color:color-mix(in lab, red, red)){.mono-pill{background:color-mix(in srgb, var(--accent) 9%, transparent)}}.badge{min-width:calc(var(--spacing) * 0);text-overflow:ellipsis;color:#6ee7b7;border:1px solid var(--success);overflow:hidden}@supports (color:color-mix(in lab, red, red)){.badge{border:1px solid color-mix(in srgb, var(--success) 34%, var(--border))}}.badge{background:#10b9811f}.badge.off,.promo-status.error,.login-status.error{color:#fca5a5}.badge.off{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.badge.off{border-color:color-mix(in srgb, var(--danger) 34%, var(--border))}}.badge.off{background:#ef44441f}.promo-status.success{color:#6ee7b7}#legal-links-login{width:min(100vw,560px)}.legal-link,.code-modal-resend{border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);--tw-font-weight:var(--font-weight-bold);font-size:13px;font-weight:var(--font-weight-bold);color:var(--text-secondary);text-underline-offset:3px;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));text-decoration-line:underline;-webkit-text-decoration-color:var(--accent);-webkit-text-decoration-color:var(--accent);text-decoration-color:var(--accent);background-color:#0000;border-width:0}@supports (color:color-mix(in lab, red, red)){.legal-link,.code-modal-resend{-webkit-text-decoration-color:color-mix(in srgb, var(--accent) 42%, currentColor);-webkit-text-decoration-color:color-mix(in srgb, var(--accent) 42%, currentColor);text-decoration-color:color-mix(in srgb, var(--accent) 42%, currentColor)}}.legal-link{flex:0 auto;display:inline}.otp-input{inset:calc(var(--spacing) * 0);z-index:2;border-style:var(--tw-border-style);color:#0000;opacity:.01;--tw-outline-style:none;caret-color:#0000;background-color:#0000;border-width:0;outline-style:none;width:100%;height:100%;position:absolute}.otp-slots{grid-template-columns:repeat(6,minmax(0,1fr))}.otp-slots span{aspect-ratio:1;min-width:calc(var(--spacing) * 0);border-radius:var(--radius-md);font-family:var(--font-mono);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-leading:1;--tw-font-weight:850;color:var(--text-primary);transition-property:border-color,background,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background:#05080d6b;place-items:center;font-weight:850;line-height:1;display:grid}.otp-slots span.filled{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.filled{border-color:color-mix(in srgb, var(--accent) 35%, var(--border))}}.otp-slots span.filled{background:var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.filled{background:color-mix(in srgb, var(--accent) 7%, #ffffff09)}}.otp-slots span.active{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.active{border-color:color-mix(in srgb, var(--accent) 72%, var(--border))}}.otp-slots span.active{box-shadow:0 0 0 3px var(--accent)}@supports (color:color-mix(in lab, red, red)){.otp-slots span.active{box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent)}}.step-num,.step-name{color:currentColor;display:block}.step-num{font-family:var(--font-mono);--tw-leading:1.1;--tw-font-weight:var(--font-weight-extrabold);font-size:11px;line-height:1.1;font-weight:var(--font-weight-extrabold)}.step-name{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1.15;--tw-font-weight:var(--font-weight-extrabold);line-height:1.15;font-weight:var(--font-weight-extrabold)}.step.active,.auth-tab.active{border-color:var(--accent);color:var(--accent);background:var(--accent)}@supports (color:color-mix(in lab, red, red)){.step.active,.auth-tab.active{background:color-mix(in srgb, var(--accent) 9%, transparent)}}.step.done{color:var(--text-secondary);border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.step.done{border-color:color-mix(in srgb, var(--accent) 45%, var(--border))}}.notice{border-radius:var(--radius-md);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;color:var(--text-secondary);padding:13px;line-height:1.45}.login-layout.show{display:flex}.telegram-login-widget{min-height:calc(var(--spacing) * 14);justify-content:center}.login-text,.promo-status{margin:calc(var(--spacing) * 0);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1.45;line-height:1.45}.promo-status{margin-top:calc(var(--spacing) * .5);overflow-wrap:anywhere;min-height:22px}.login-status{margin:calc(var(--spacing) * 0);min-height:calc(var(--spacing) * 5)}.toast{z-index:40;border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-bold);width:max-content;max-width:calc(100vw - 28px);font-size:13px;font-weight:var(--font-weight-bold);color:var(--text-primary);opacity:0;transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:cubic-bezier(.2,.8,.2,1);left:50%;bottom:max(var(--app-safe-bottom), 22px);border:1px solid var(--accent);padding-block:11px;padding-inline:13px;transition-duration:.3s;transition-timing-function:cubic-bezier(.2,.8,.2,1);position:fixed}@supports (color:color-mix(in lab, red, red)){.toast{border:1px solid color-mix(in srgb, var(--accent) 45%, var(--border))}}.toast{-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);box-shadow:0 16px 38px #00000073, 0 0 15px var(--accent);background:#0f1521d1}@supports (color:color-mix(in lab, red, red)){.toast{box-shadow:0 16px 38px #00000073, 0 0 15px color-mix(in srgb, var(--accent) 15%, transparent)}}.toast{pointer-events:none;transform:translate(-50%,14px)scale(.94)}.toast.show{opacity:1;transform:translate(-50%)scale(1)}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.z-\[1\]{z-index:1}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-5{height:calc(var(--spacing) * 5)}.min-h-14{min-height:calc(var(--spacing) * 14)}.w-5{width:calc(var(--spacing) * 5)}.w-12{width:calc(var(--spacing) * 12)}.w-\[min\(100\%\,420px\)\]{width:min(100%,420px)}.w-full{width:100%}.max-w-\[320px\]{max-width:320px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-12{min-width:calc(var(--spacing) * 12)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-text{cursor:text}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[minmax\(0\,0\.6fr\)_minmax\(0\,1\.4fr\)\]{grid-template-columns:minmax(0,.6fr) minmax(0,1.4fr)}.grid-cols-\[minmax\(0\,0\.8fr\)_minmax\(0\,1\.2fr\)\]{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr)}.grid-cols-\[minmax\(0\,1fr\)_minmax\(120px\,auto\)\]{grid-template-columns:minmax(0,1fr) minmax(120px,auto)}.justify-items-center{justify-items:center}.justify-items-stretch{justify-items:stretch}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-\[17px\]{gap:17px}.rounded-\[var\(--radius-md\)\]{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-\[var\(--border\)\]{border-color:var(--border)}.bg-\[rgba\(255\,255\,255\,0\.03\)\]{background-color:#ffffff08}.bg-\[rgba\(255\,255\,255\,0\.015\)\]{background-color:#ffffff04}.fill-current{fill:currentColor}.p-0{padding:calc(var(--spacing) * 0)}.p-3\.5{padding:calc(var(--spacing) * 3.5)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-\[17px\]{padding:17px}.p-\[18px\]{padding:18px}.px-0{padding-inline:calc(var(--spacing) * 0)}.pt-3\.5{padding-top:calc(var(--spacing) * 3.5)}.text-center{text-align:center}.font-\[family-name\:var\(--font-mono\)\]{font-family:var(--font-mono)}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[20px\]{font-size:20px}.text-\[22px\]{font-size:22px}.text-\[28px\]{font-size:28px}.leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}.leading-\[1\.05\]{--tw-leading:1.05;line-height:1.05}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.tracking-\[0\]{--tw-tracking:0;letter-spacing:0}.break-words{overflow-wrap:break-word}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.uppercase{text-transform:uppercase}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media not all and (min-width:560px){.max-\[560px\]\:flex-\[1_1_100\%\]{flex:100%}}@media not all and (min-width:460px){.max-\[460px\]\:max-w-\[240px\]{max-width:240px}.max-\[460px\]\:text-\[24px\]{font-size:24px}}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}:root{color-scheme:dark;--bg-primary:#05070a;--bg-card:#0f1521;--bg-card-hover:#161e2e;--border:#1e2940;--text-primary:#e2e8f0;--text-secondary:#94a3b8;--text-muted:#64748b;--accent:#00fe7a;--accent-hover:#5aff9f;--success:#10b981;--danger:#ef4444;--font-sans:Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:"JetBrains Mono", "Fira Code", monospace;--radius-sm:6px;--radius-md:8px;--radius-lg:8px;--transition:.2s cubic-bezier(.4, 0, .2, 1);--app-safe-top-extra:10px;--app-safe-top:max(var(--tg-content-safe-area-inset-top,0px), var(--tg-safe-area-inset-top,0px), env(safe-area-inset-top));--app-safe-bottom:max(var(--tg-content-safe-area-inset-bottom,0px), var(--tg-safe-area-inset-bottom,0px), env(safe-area-inset-bottom))}.hidden{display:none!important}@supports not (color:color-mix(in srgb, #000 50%, #fff)){.mono-pill,.step.active,.auth-tab.active{border-color:var(--accent);background:#00fe7a14}}@media (hover:hover) and (pointer:fine){.legal-link:hover:not(:disabled),.code-modal-resend:hover:not(:disabled){color:var(--accent)}}@media (max-width:460px){.app{padding-inline:calc(var(--spacing) * 2.5)}.user-chip{max-width:150px}.user-chip-name{display:none}.brand-title{max-width:164px}.metric{grid-template-columns:repeat(1,minmax(0,1fr))}.badge,.mono-pill{max-width:100%}.otp-slots{gap:calc(var(--spacing) * 1.5)}.otp-slots span{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.brand-logo--lg{width:68px;height:68px}.brand-logo-shell--lg{width:96px;height:96px}.login-auth-body{min-height:128px}.login-brand-title{max-width:200px;font-size:21px}}@media (max-width:380px){.brand-title{max-width:150px}}@media (max-width:560px){.step-box{min-height:54px;padding-inline:7px;padding-block:calc(var(--spacing) * 2)}.modal{padding:calc(var(--spacing) * 0);padding:calc(max(var(--app-safe-top), 14px) + var(--app-safe-top-extra)) 14px max(var(--app-safe-bottom), 14px);place-items:center;top:0;bottom:0}.modal--page{padding:calc(var(--spacing) * 0);place-items:stretch stretch;padding:0!important}.modal--page .modal-backdrop{background-color:var(--bg-primary);opacity:1;-webkit-backdrop-filter:none;backdrop-filter:none}.modal-card{border-radius:var(--radius-lg);width:100%;height:auto;max-height:min(92vh,760px);transform:translateY(12px)scale(.98)}.modal--page .modal-card{border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;width:100%;max-width:none;height:100%;max-height:none;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-width:0;border-radius:0;transform:none!important}.modal.show .modal-card{transform:translateY(0)scale(1)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file diff --git a/bot/app/web/templates/subscription_webapp.html b/bot/app/web/templates/subscription_webapp.html index 03da990..7d151ff 100644 --- a/bot/app/web/templates/subscription_webapp.html +++ b/bot/app/web/templates/subscription_webapp.html @@ -291,6 +291,6 @@ - + diff --git a/bot/app/web/templates/subscription_webapp.js b/bot/app/web/templates/subscription_webapp.js index 31fe036..7482cd9 100644 --- a/bot/app/web/templates/subscription_webapp.js +++ b/bot/app/web/templates/subscription_webapp.js @@ -435,7 +435,7 @@ 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) || FALLBACK_I18N; + const I18N = readJsonScript('i18n') || (MOCK && MOCK.i18n) || {}; const accent = CFG.primaryColor || '#00fe7a'; document.documentElement.style.setProperty('--accent', accent); @@ -2440,14 +2440,14 @@ const MOCK = (() => { if (document.documentElement.lang) candidates.push(document.documentElement.lang); for (const raw of candidates) { const short = String(raw).toLowerCase().split('-')[0]; - if (I18N[short]) return short; + if (I18N && I18N[short]) return short; } return 'ru'; } function setLanguage(lang) { const short = String(lang || '').toLowerCase().split('-')[0]; - if (!I18N[short]) return; + if (I18N && Object.keys(I18N).length && !I18N[short]) return; state.langOverride = short; try { localStorage.setItem('rw_webapp_lang', short); } catch (e) { } applyI18n(); @@ -2459,8 +2459,8 @@ const MOCK = (() => { } function t(key, params = {}) { - const table = I18N[getLanguage()] || I18N.ru; - const fallback = I18N.ru[key] || key; + const table = (I18N && I18N[getLanguage()]) || (I18N && I18N.ru) || {}; + const fallback = (I18N && I18N.ru && I18N.ru[key]) || key; const template = table[key] || fallback; return template.replace(/\{(\w+)\}/g, (_, name) => ( Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : '' diff --git a/bot/app/web/templates/subscription_webapp.min.bc7b9bbe.js b/bot/app/web/templates/subscription_webapp.min.bc7b9bbe.js new file mode 100644 index 0000000..8706607 --- /dev/null +++ b/bot/app/web/templates/subscription_webapp.min.bc7b9bbe.js @@ -0,0 +1,79 @@ +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")||"",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)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),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),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>>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+='',m!==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"))}
+
${escapeHtml(t("account_merge_caption"))}
+
+ +
+ +
+
${escapeHtml(t("account_merge_body"))}
+
+ +
+
+
${escapeHtml(t("account_merge_primary_account_label"))}
+
#${escapeHtml(i)}
+
+
+
${escapeHtml(t("account_merge_removed_account_label"))}
+
#${escapeHtml(o)}
+
+
+
${escapeHtml(t("account_merge_end_date_label"))}
+
${escapeHtml(r)}
+
+
+ `,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=>` +
+ ${escapeHtml(o.title||"")} + ${escapeHtml(t("referral_bonus_pair",{inviter:o.inviter_days||0,friend:o.friend_days||0}))} +
+ `).join("");a.innerHTML=` +
+
+
${escapeHtml(t("referral_title"))}
+
${escapeHtml(t("referral_caption"))}
+
+ +
+ +
+
+ + +
+ +
+
+ +
${escapeHtml(t("referral_bonus_explanation"))}
+
+
${i||'
'+escapeHtml(t("referral_no_bonuses"))+"
"}
+
+
+ `,applyI18n(n),window.requestAnimationFrame(()=>n.classList.add("show"))}function renderReferralLinkRow(e,n,a){return` +
+
+
${escapeHtml(n)}
+
${escapeHtml(a||t("not_available"))}
+
+ +
+ `}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),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),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='
'+escapeHtml(t("no_plans"))+"
";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` + + `}).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='
'+escapeHtml(t("choose_plan_first"))+"
";return}const n=getAvailableMethods();if(!n.length){e.innerHTML='
'+escapeHtml(t("no_methods"))+"
";return}e.innerHTML=n.map(a=>{const i=a.id==="platega"?"pay_with_platega_button":"pay_with_cryptopay_button",o=a.id==="platega"?TW.paymentMethodCardPlatega:TW.paymentMethodCardCryptopay;return` + + `}).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","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=Object.assign({Authorization:"Bearer "+state.token},n.headers||{});n.body&&!a["Content-Type"]&&(a["Content-Type"]="application/json");const i=await fetch(CFG.apiBase+e,Object.assign({},n,{headers:a}));if(i.status===401)throw clearToken(),await startExternalAuth(),new Error("Unauthorized");return i.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"}:e==="/account/email/verify"?(MOCK.data.user.email="preview@example.com",MOCK.data.user.email_verified=!0,{ok:!0,token:"local-preview"}):e==="/account/telegram/link"?(MOCK.data.user.telegram_linked=!0,MOCK.data.user.telegram_id=100200300,{ok:!0,token:"local-preview"}):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){state.token=e,localStorage.setItem("rw_webapp_token",e)}function clearToken(){state.token="",localStorage.removeItem("rw_webapp_token")}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),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("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function escapeAttr(e){return escapeHtml(e).replaceAll("`","`")} diff --git a/bot/app/web/templates/subscription_webapp.tailwind.css b/bot/app/web/templates/subscription_webapp.tailwind.css index a486148..0e0e9ff 100644 --- a/bot/app/web/templates/subscription_webapp.tailwind.css +++ b/bot/app/web/templates/subscription_webapp.tailwind.css @@ -228,7 +228,7 @@ } .lang-dropdown-item.is-active { - @apply text-[var(--accent)]; + @apply bg-[var(--bg-card-hover)] text-[var(--text-primary)]; } .lang-dropdown-flag { diff --git a/package-lock.json b/package-lock.json index 965427e..66ea61d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,452 @@ "": { "devDependencies": { "@tailwindcss/cli": "4.2.4", + "esbuild": "^0.28.0", "tailwindcss": "4.2.4" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -698,6 +1141,48 @@ "node": ">=10.13.0" } }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", diff --git a/package.json b/package.json index 10e2602..2ec6f43 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "private": true, "scripts": { - "build:webapp:css": "tailwindcss -i ./bot/app/web/templates/subscription_webapp.tailwind.css -o ./bot/app/web/templates/subscription_webapp.css --minify" + "build:webapp:css": "tailwindcss -i ./bot/app/web/templates/subscription_webapp.tailwind.css -o ./bot/app/web/templates/subscription_webapp.css --minify", + "build:webapp:js": "node ./scripts/build_subscription_webapp_js.mjs", + "build:webapp": "npm run build:webapp:css && npm run build:webapp:js" }, "devDependencies": { "@tailwindcss/cli": "4.2.4", + "esbuild": "^0.28.0", "tailwindcss": "4.2.4" } } diff --git a/scripts/build_subscription_webapp_js.mjs b/scripts/build_subscription_webapp_js.mjs new file mode 100644 index 0000000..2c4f947 --- /dev/null +++ b/scripts/build_subscription_webapp_js.mjs @@ -0,0 +1,90 @@ +#!/usr/bin/env node +import { createHash } from "node:crypto"; +import { readFile, readdir, unlink, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { transform } from "esbuild"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, ".."); +const sourcePath = path.join( + repoRoot, + "bot", + "app", + "web", + "templates", + "subscription_webapp.js", +); + +function normalizeLineEndings(value) { + return value.replace(/\r\n/g, "\n"); +} + +function stripMarkedBlock(source, startMarker, endMarker) { + const start = source.indexOf(startMarker); + if (start === -1) { + return source; + } + const end = source.indexOf(endMarker, start); + if (end === -1) { + return source.slice(0, start); + } + return source.slice(0, start) + source.slice(end + endMarker.length); +} + +function stripFallbackI18n(source) { + const fallbackStart = source.indexOf(" const FALLBACK_I18N = {"); + const i18nLine = " const I18N = readJsonScript('i18n') || (MOCK && MOCK.i18n) || FALLBACK_I18N;"; + const i18nLineIndex = source.indexOf(i18nLine); + if (fallbackStart === -1 || i18nLineIndex === -1 || i18nLineIndex < fallbackStart) { + return source; + } + + return ( + source.slice(0, fallbackStart) + + " const I18N = readJsonScript('i18n') || (MOCK && MOCK.i18n) || {};\n" + + source.slice(i18nLineIndex + i18nLine.length) + ); +} + +async function removeOldMinifiedAssets(assetDir, keepName) { + const entries = await readdir(assetDir, { withFileTypes: true }); + await Promise.all( + entries + .filter( + (entry) => entry.isFile() && /^subscription_webapp\.min\.[0-9a-f]{8}\.js$/.test(entry.name) && entry.name !== keepName, + ) + .map((entry) => unlink(path.join(assetDir, entry.name))), + ); +} + +async function main() { + const rawSource = await readFile(sourcePath, "utf8"); + const withoutMocks = stripMarkedBlock( + normalizeLineEndings(rawSource), + "/* WEBAPP_DEV_MOCK_START */", + "/* WEBAPP_DEV_MOCK_END */", + ); + const strippedSource = stripFallbackI18n(withoutMocks); + const result = await transform(strippedSource, { + charset: "utf8", + legalComments: "none", + loader: "js", + minify: true, + target: "es2018", + }); + + const code = `${result.code.replace(/[ \t]+$/gm, "").trimEnd()}\n`; + const hash = createHash("sha256").update(code, "utf8").digest("hex").slice(0, 8); + const outputPath = path.join( + path.dirname(sourcePath), + `subscription_webapp.min.${hash}.js`, + ); + + await removeOldMinifiedAssets(path.dirname(sourcePath), path.basename(outputPath)); + await writeFile(outputPath, code, "utf8"); + console.log(`Wrote ${path.relative(repoRoot, outputPath)} (${Buffer.byteLength(code, "utf8")} bytes)`); +} + +await main(); diff --git a/tests/test_webapp_assets.py b/tests/test_webapp_assets.py new file mode 100644 index 0000000..db24f97 --- /dev/null +++ b/tests/test_webapp_assets.py @@ -0,0 +1,44 @@ +import os +import tempfile +import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +from bot.app.web import subscription_webapp + + +class WebAppAssetTests(unittest.IsolatedAsyncioTestCase): + def test_resolve_webapp_js_asset_name_prefers_latest_minified_build(self): + with tempfile.TemporaryDirectory() as tmpdir: + asset_dir = Path(tmpdir) + (asset_dir / "subscription_webapp.js").write_text("console.log('fallback');", encoding="utf-8") + old_asset = asset_dir / "subscription_webapp.min.11111111.js" + new_asset = asset_dir / "subscription_webapp.min.22222222.js" + old_asset.write_text("console.log('old');", encoding="utf-8") + new_asset.write_text("console.log('new');", encoding="utf-8") + os.utime(old_asset, (1, 1)) + os.utime(new_asset, (2, 2)) + + with patch.object(subscription_webapp, "ASSET_DIR", asset_dir): + self.assertEqual( + subscription_webapp._resolve_webapp_js_asset_name(), + "subscription_webapp.min.22222222.js", + ) + + async def test_js_asset_route_sets_immutable_cache_control_for_minified_asset(self): + with tempfile.TemporaryDirectory() as tmpdir: + asset_dir = Path(tmpdir) + minified_asset = asset_dir / "subscription_webapp.min.abcdef12.js" + minified_asset.write_text("console.log('minified');", encoding="utf-8") + + request = SimpleNamespace( + app={"settings": SimpleNamespace(WEBAPP_ENABLED=True)}, + match_info={"asset_hash": "abcdef12"}, + ) + + with patch.object(subscription_webapp, "ASSET_DIR", asset_dir): + response = await subscription_webapp.js_asset_route(request) + + self.assertEqual(response.headers["Cache-Control"], "public, max-age=31536000, immutable") + self.assertEqual(response.text, "console.log('minified');")