PageSourceSearch

https://aidapp.in/app/public/modules/vpn/js/vpn-stories-content.js?v=1.0.20

js aidapp.in collected 2026-10-09 03:48:53 UTC 23,697 bytes, 432 lines download raw bytes

1(function() {
2  'use strict';
3  function escapeHtml(s) {
4    return String(s == null ? '' : s).replace(/[&<>"']/g, (function(c) {
5      return {
6        '&': '&amp;',
7        '<': '&lt;',
8        '>': '&gt;',
9        '"': '&quot;',
10        "'": '&#39;'
11      }[c];
12    }));
13  }
14  function appsIcon(file) {
15    if (typeof window.assetUrl === 'function') return window.assetUrl(file);
16    return (window.APP_BASE || '/app/') + 'public/' + file;
17  }
18  function fallbackAppIcon() {
19    return appsIcon('img/svg/app-default.svg');
20  }
21  function withPulse(html, selector) {
22    if (!selector) return html;
23    try {
24      var tpl = document.createElement('div');
25      tpl.innerHTML = html;
26      var target = tpl.querySelector(selector);
27      if (target) target.classList.add('is-pulse');
28      return tpl.innerHTML;
29    } catch (e) {
30      return html;
31    }
32  }
33  function pickDemoKey() {
34    var keys = window.AuthModule && window.AuthModule.vpnKeys || [];
35    if (Array.isArray(keys)) {
36      for (var i = 0; i < keys.length; i++) {
37        var k = keys[i];
38        if (k && (k.protocol || '').toLowerCase() === 'vless') return k;
39      }
40    }
41    return makeFakeVlessKey();
42  }
43  function makeFakeVlessKey() {
44    var nowSec = Math.floor(Date.now() / 1e3);
45    return {
46      id: 'demo',
47      name: 'demo-aid-vless',
48      username: 'demo-aid-vless',
49      cstname: 'Демо-ключ',
50      sub_link: '',
51      subscription_url: '',
52      pay_end_time: nowSec + 24 * 86400,
53      expires_at: nowSec + 24 * 86400,
54      status: 'active',
55      protocol: 'vless',
56      clients_count: 1,
57      addons: [],
58      location: null,
59      is_v2: true
60    };
61  }
62  function detectOS() {
63    try {
64      if (window.OSDetection && typeof window.OSDetection.detect === 'function') {
65        var os = window.OSDetection.detect();
66        if (os === 'macos') return 'macOS';
67        if (os === 'windows') return 'Windows';
68        if (os === 'ios') return 'iOS';
69        return 'Android';
70      }
71    } catch (e) {}
72    return 'iOS';
73  }
74  function appsForOS(osLabel) {
75    try {
76      var data = window.getVpnAppsData ? window.getVpnAppsData() : null;
77      var key = osLabel === 'Windows' ? 'Windows11' : osLabel === 'macOS' ? 'MacOS' : osLabel;
78      var list = data && data[key] || [];
79      list = list.slice().sort((function(a, b) {
80        var ar = a.badge === 'recommended' || a.like ? -1 : 0;
81        var br = b.badge === 'recommended' || b.like ? -1 : 0;
82        return ar - br;
83      }));
84      return list.slice(0, 3);
85    } catch (e) {
86      return [];
87    }
88  }
89  function badgeOf(app) {
90    if (!app) return null;
91    if (app.badge === 'recommended' || app.like === true) return {
92      cls: 'recommended',
93      text: 'Рек'
94    };
95    if (app.badge === 'pro') return {
96      cls: 'pro',
97      text: 'Pro'
98    };
99    if (app.badge === 'alternative') return {
100      cls: 'alternative',
101      text: 'Альт'
102    };
103    return null;
104  }
105  var MOCKUPS = {
106    keyCard: function(opts) {
107      var pulseTarget = opts && opts.highlight || null;
108      var pulseSelector = null;
109      if (pulseTarget === 'manage') pulseSelector = '.tariff-button--manage'; else if (pulseTarget === 'renew') pulseSelector = '.tariff-button:not(.tariff-button--manage):not(.tariff-button--delete)';
110      var key = pickDemoKey();
111      if (key && typeof window.createKeyCard === 'function') {
112        try {
113          var realHtml = window.createKeyCard(key);
114          return '<div class="vpnst-mock vpnst-mock-realkey">' + withPulse(realHtml, pulseSelector) + '</div>';
115        } catch (e) {}
116      }
117      return '' + '<div class="vpnst-mock vpnst-mock-keycard">' + '<div class="vpnst-mock-keycard__protocol">VLESS</div>' + '<span class="vpnst-mock-keycard__status">Активен</span>' + '<div class="vpnst-mock-keycard__name">Мой ключ</div>' + '<div class="vpnst-mock-keycard__id">#5f3a</div>' + '<div class="vpnst-mock-keycard__time">осталось 24 дня</div>' + '<div class="vpnst-mock-keycard__actions">' + '<button class="vpnst-mock-keycard__action ' + (pulseTarget === 'manage' ? 'is-pulse' : '') + '">Мой ключ</button>' + '<button class="vpnst-mock-keycard__action vpnst-mock-keycard__action--ghost ' + (pulseTarget === 'renew' ? 'is-
117pulse' : '') + '">Продлить</button>' + '</div>' + '</div>';
118    },
119    appsGrid: function(opts) {
120      var activeOs = opts && opts.os || detectOS();
121      var noBadges = !!(opts && opts.noBadges);
122      var oss = [ 'Android', 'iOS', 'Windows', 'macOS' ];
123      var btns = '';
124      for (var i = 0; i < oss.length; i++) {
125        btns += '<button class="toggle-switch__btn' + (oss[i] === activeOs ? ' toggle-switch__btn--active' : '') + '">' + oss[i] + '</button>';
126      }
127      var apps = appsForOS(activeOs);
128      if (!apps.length) {
129        apps = [ {
130          name: 'Happ',
131          image: 'img/external/happ-icon.webp',
132          badge: 'recommended'
133        }, {
134          name: 'V2RayTun',
135          image: 'img/external/v2raytun-icon.webp',
136          badge: 'alternative'
137        } ];
138      }
139      var iconsHtml = '';
140      for (var j = 0; j < apps.length; j++) {
141        var app = apps[j];
142        var b = noBadges ? null : badgeOf(app);
143        var pulse = j === 0 ? ' is-pulse' : '';
144        iconsHtml += '<div class="vpn-app-icon' + (b ? ' vpn-app-icon--' + b.cls : '') + pulse + '">' + (b ? '<div class="vpn-app-badge vpn-app-badge--' + b.cls + '">' + b.text + '</div>' : '') + '<img src="' + appsIcon(app.image || 'img/svg/app-default.svg') + '" ' + 'alt="' + escapeHtml(app.name) + '" class="vpn-app-icon__image" ' + 'onerror="this.src=\'' + fallbackAppIcon() + '\'" />' + '<div class="vpn-app-icon__name">' + escapeHtml(app.name) + '</div>' + '</div>';
145      }
146      return '' + '<div class="vpnst-mock vpnst-mock-apps-real">' + '<div class="toggle-switch">' + '<div class="toggle-switch__bg">' + btns + '</div>' + '</div>' + '<div class="vpn_app_icons">' + iconsHtml + '</div>' + '</div>';
147    },
148    installFlow: function() {
149      return MOCKUPS._flow({
150        variant: 'install'
151      });
152    },
153    addKeyFlow: function() {
154      return MOCKUPS._flow({
155        variant: 'addkey'
156      });
157    },
158    _flow: function(opts) {
159      var variant = opts && opts.variant || 'install';
160      var iconUrl = appsIcon('img/external/happ-icon.webp');
161      var fallback = fallbackAppIcon();
162      var imgErr = ' onerror="this.src=\'' + fallback + '\'"';
163      var appsHtml = MOCKUPS.appsGrid({
164        noBadges: true
165      });
166      var appsBlock = '<div class="vpnst-mock-flow__apps">' + appsHtml + '</div>';
167      var sheet = '' + '<div class="vpnst-mock-flow__sheet">' + '<div class="vpnst-mock-flow__sheet-row">' + '<img src="' + iconUrl + '" alt="" class="vpnst-mock-flow__sheet-icon"' + imgErr + ' />' + '<div class="vpnst-mock-flow__sheet-text">' + '<div class="vpnst-mock-flow__sheet-name">Happ</div>' + '<div class="vpnst-mock-flow__sheet-meta">VPN-клиент</div>' + '</div>' + '</div>' + '<div class="vpnst-mock-flow__sheet-buttons">' + '<button type="button" class="vpnst-mock-flow__sheet-btn vpnst-mock-flow__sheet-btn--blue">Установить</button>' + '<button type="button" class="vpnst-mock-flow__sheet-btn vpnst-mock-flow__sheet-btn--gray">Добавить ключ</button>' + '</div>' + '</div>';
168      var installSuccessSvg = '<svg class="vpnst-mock-flow__installing-success-icon" viewBox="0 0 16 16" aria-hidden="true">' + '<path fill="none" stroke="currentColor" stroke-width="2.4" ' + 'stroke-linecap="round" stroke-linejoin="round" d="M3.5 8.5 6.7 11.7 12.7 5"/>' + '</svg>';
169      var installFinal = variant === 'install' ? '<div class="vpnst-mock-flow__installing">' + '<div class="vpnst-mock-flow__installing-icon">' + '<img src="' + iconUrl + '" alt=""' + imgErr + ' />' + '</div>' + '<div class="vpnst-mock-flow__installing-bar"><i></i></div>' + '<div class="vpnst-mock-flow__installing-success" aria-hidden="true">' + installSuccessSvg + '<span class="vpnst-mock-flow__installing-success-text">Приложение установлено</span>' + '</div>' + '</div>' : '';
170      var addKeyFinal = '';
171      if (variant === 'addkey') {
172        var locations = [ {
173          flag: '🇩🇪',
174          name: 'Germany',
175          active: true
176        }, {
177          flag: '🇳🇱',
178          name: 'Netherlands',
179          active: false
180        }, {
181          flag: '🇸🇪',
182          name: 'Sweden',
183          active: false
184        } ];
185        var checkSvg = '<svg viewBox="0 0 16 16" aria-hidden="true">' + '<path fill="none" stroke="currentColor" stroke-width="2.4" ' + 'stroke-linecap="round" stroke-linejoin="round" ' + 'd="M3.5 8.5 6.7 11.7 12.7 5"/>' + '</svg>';
186        var locsHtml = '';
187        for (var li = 0; li < locations.length; li++) {
188          var L = locations[li];
189          locsHtml += '<div class="vpnst-mock-flow__happ-loc' + (L.active ? ' vpnst-mock-flow__happ-loc--active' : '') + '"' + ' style="--li:' + li + '">' + '<span class="vpnst-mock-flow__happ-loc-flag">' + L.flag + '</span>' + '<span class="vpnst-mock-flow__happ-loc-name">' + escapeHtml(L.name) + '</span>' + '<span class="vpnst-mock-flow__happ-loc-check" aria-label="добавлено">' + checkSvg + '</span>' + '</div>';
190        }
191        addKeyFinal = '<div class="vpnst-mock-flow__happ">' + '<div class="vpnst-mock-flow__happ-header">' + '<img src="' + iconUrl + '" alt="" class="vpnst-mock-flow__happ-logo"' + imgErr + ' />' + '<div class="vpnst-mock-flow__happ-title">Happ</div>' + '<div class="vpnst-mock-flow__happ-sub">Ключ добавлен</div>' + '</div>' + '<div class="vpnst-mock-flow__happ-list">' + locsHtml + '</div>' + '</div>';
192      }
193      var rootCls = 'vpnst-mock vpnst-mock-flow vpnst-mock-flow--' + variant;
194      return '<div class="' + rootCls + '">' + appsBlock + sheet + installFinal + addKeyFinal + '</div>';
195    },
196    manageToggle: function(opts) {
197      var which = opts && opts.tab || 'change';
198      var u = which === 'manage' ? ' toggle-switch__btn--active is-pulse' : '';
199      var c = which === 'change' ? ' toggle-switch__btn--active is-pulse' : '';
200      var indicatorPos = which === 'manage' ? 'left' : 'right';
201      return '' + '<div class="vpnst-mock vpnst-mock-managetoggle">' + '<div class="toggle-switch">' + '<div class="toggle-switch__bg">' + '<div class="toggle-switch__indicator vpnst-mt-indicator vpnst-mt-indicator--' + indicatorPos + '"></div>' + '<button class="toggle-switch__btn' + u + '">Управление</button>' + '<button class="toggle-switch__btn' + c + '">Смена тарифа</button>' + '</div>' + '</div>' + '<div class="vpnst-mock-managetoggle__caption">' + 'Внутри карточки управления переключитесь на вкладку <b>«Смена тарифа»</b>.' + '</div>' + '</div>';
202    },
203    tariffChangeUI: function() {
204      function stack(items) {
205        var inner = '';
206        for (var i = 0; i < items.length; i++) {
207          inner += '<span class="vpnst-tc-val vpnst-tc-val--' + items[i].cls + '">' + escapeHtml(items[i].text) + '</span>';
208        }
209        return '<span class="vpnst-tc-stack">' + inner + '</span>';
210      }
211      var deviceCount = stack([ {
212        cls: 'a',
213        text: '3'
214      }, {
215        cls: 'bc',
216        text: '4'
217      } ]);
218      var newTerm = stack([ {
219        cls: 'a',
220        text: '14д'
221      }, {
222        cls: 'b',
223        text: '9д'
224      }, {
225        cls: 'c',
226        text: '5д'
227      } ]);
228      return '' + '<div class="vpnst-mock vpnst-mock-tariffchange">' + '<div class="vpnst-tc-card">' + '<div class="clients-slider-container">' + '<div class="clients-slider-header">' + '<span class="clients-slider-label">Количество устройств</span>' + '<span class="clients-slider-value">' + deviceCount + '</span>' + '</div>' + '<div class="clients-slider-wrapper">' + '<div class="clients-slider-track">' + '<div class="clients-slider-fill vpnst-tc-fill"></div>' + '</div>' + '<div class="vpnst-tc-thumb"></div>' + '</div>' + '</div>' + '<div class="vpnst-tc-divider"></div>' + '<div class="addon-item">' + '<div class="addon-toggle">' + '<div class="addon-toggle-track vpnst-tc-toggle-track"></div>' + '<div class="addon-toggle-thumb vpnst-tc-toggle-thumb"></div>' + '</div>' + '<div class="addon-info">' + '<div class="addon-name">Белый список</div>' + '<div class="addon-desc">Сайты в обход VPN, ходят напрямую</div>' + '</div>' + '<div class="addon-price">+50₽</div>' + '</div>' + '<div class="vpnst-tc-divider"></div>' + '<div class="vpn-time-recalc-row">' + '<div class="vpn-time-recalc-col">' + '<div class="vpn-time-recalc-label">Текущий срок</div>' + '<div class="vpn-time-recalc-value vpn-time-recalc-value--neutral">24д</div>' + '</div>' + '<div class="vpn-time-recalc-arrow">→</div>' + '<div class="vpn-time-recalc-col">' + '<div class="vpn-time-recalc-label">Новый срок</div>' + '<div class="vpn-time-recalc-value vpn-time-recalc-value--negative">' + newTerm + '</div>' + '</div>' + '</div>' + '<button class="button-blue-primary vnmk-button vpnst-tc-button" type="button">Изменить тариф</button>' + '</div>' + '</div>';
229    },
230    renewUI: function() {
231      function rnStack(items) {
232        var inner = '';
233        for (var i = 0; i < items.length; i++) {
234          inner += '<span class="vpnst-tc-val vpnst-rn-val--' + items[i].cls + '">' + items[i].text + '</span>';
235        }
236        return '<span class="vpnst-tc-stack">' + inner + '</span>';
237      }
238      function priceVal(num) {
239        return escapeHtml(num) + '<i class="vpnst-rn-cur">₽</i>';
240      }
241      var amount = rnStack([ {
242        cls: 'a',
243        text: priceVal('199')
244      }, {
245        cls: 'b',
246        text: priceVal('597')
247      }, {
248        cls: 'c',
249        text: priceVal('1075')
250      }, {
251        cls: 'd',
252        text: priceVal('2030')
253      } ]);
254      var oldPrice = rnStack([ {
255        cls: 'a',
256        text: ''
257      }, {
258        cls: 'b',
259        text: ''
260      }, {
261        cls: 'c',
262        text: priceVal('1194')
263      }, {
264        cls: 'd',
265        text: priceVal('2388')
266      } ]);
267      var period = rnStack([ {
268        cls: 'a',
269        text: escapeHtml('1 мес')
270      }, {
271        cls: 'b',
272        text: escapeHtml('3 мес')
273      }, {
274        cls: 'c',
275        text: escapeHtml('6 мес')
276      }, {
277        cls: 'd',
278        text: escapeHtml('12 мес')
279      } ]);
280      var discountText = rnStack([ {
281        cls: 'c',
282        text: escapeHtml('-10% за 6 мес')
283      }, {
284        cls: 'd',
285        text: escapeHtml('-15% за 12 мес')
286      } ]);
287      return '' + '<div class="vpnst-mock vpnst-mock-renew">' + '<div class="vpnst-rn-card">' + '<div class="toggle-switch">' + '<div class="toggle-switch__bg">' + '<div class="toggle-switch__indicator vpnst-rn-indicator"></div>' + '<button type="button" class="toggle-switch__btn vpnst-rn-btn vpnst-rn-btn--a">1 мес</button>' + '<button type="button" class="toggle-switch__btn vpnst-rn-btn vpnst-rn-btn--b">3 мес</button>' + '<button type="button" class="toggle-switch__btn vpnst-rn-btn vpnst-rn-btn--c">6 мес</button>' + '<button type="button" class="toggle-switch__btn vpnst-rn-btn vpnst-rn-btn--d">12 мес</button>' + '</div>' + '</div>' + '<div class="vpnst-tc-divider"></div>' + '<div class="vpnst-rn-summary">' + '<div class="purchase-bar">' + '<div class="purchase-bar__info">' + '<div class="purchase-bar__price">' + '<span class="purchase-bar__amount">' + amount + '</span>' + '<span class="purchase-bar__old">' + oldPrice + '</span>' + '</div>' + '<div class="purchase-bar__details">' + '<span>VLESS</span>' + '<span class="purchase-bar__dot">•</span>' + '<span>' + period + '</span>' + '</div>' + '</div>' + '</div>' + '<div class="vpnst-rn-discount">' + discountText + '</div>' + '</div>' + '<button class="button-blue-primary vnmk-button vpnst-rn-button" type="button">Продлить</button>' + '</div>' + '</div>';
288    },
289    renewSuccess: function() {
290      return '' + '<div class="vpnst-mock vpnst-mock-renewsuccess">' + '<div class="vpnst-mock-renewsuccess__icon">' + '<svg viewBox="0 0 24 24" aria-hidden="true">' + '<path d="M5 12.5 L10 17.5 L19 7"/>' + '</svg>' + '</div>' + '<div class="vpnst-mock-renewsuccess__title">Готово!</div>' + '<div class="vpnst-mock-renewsuccess__sub">VPN ключ успешно продлён</div>' + '<div class="vpnst-mock-renewsuccess__balance">' + 'Списано с баланса AID: <b>1 075 ₽</b>' + '</div>' + '</div>';
291    }
292  };
293  var BASE_BOTTOM = '#0b0d10';
294  var THEMES = {
295    blue: {
296      gradTop: '#3b82f6',
297      gradBottom: BASE_BOTTOM
298    },
299    indigo: {
300      gradTop: '#6366f1',
301      gradBottom: BASE_BOTTOM
302    },
303    violet: {
304      gradTop: '#a855f7',
305      gradBottom: BASE_BOTTOM
306    },
307    magenta: {
308      gradTop: '#d946ef',
309      gradBottom: BASE_BOTTOM
310    },
311    amber: {
312      gradTop: '#f59e0b',
313      gradBottom: BASE_BOTTOM
314    },
315    green: {
316      gradTop: '#10b981',
317      gradBottom: BASE_BOTTOM
318    },
319    teal: {
320      gradTop: '#14b8a6',
321      gradBottom: BASE_BOTTOM
322    }
323  };
324  var FA_ICONS = {
325    install: '<svg viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M144 480C64.5 480 0 415.5 0 336c0-62.8 40.2-116.2 96.2-135.9c-.1-2.7-.2-5.4-.2-8.1c0-88.4 71.6-160 160-160c59.3 0 111 32.2 138.7 80.2C409.9 102 428.3 96 448 96c53 0 96 43 96 96c0 12.2-2.3 23.8-6.4 34.6C596 238.4 640 290.1 640 352c0 70.7-57.3 128-128 128H144zm79-167l72 72c9.4 9.4 24.6 9.4 33.9 0l72-72c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-31 31V224c0-13.3-10.7-24-24-24s-24 10.7-24 24v86.1l-31-31c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9z"/></svg>',
326    tariff: '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M0 416c0 17.7 14.3 32 32 32l54.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 448c17.7 0 32-14.3 32-32s-14.3-32-32-32l-246.7 0c-12.3-28.3-40.5-48-73.3-48s-61 19.7-73.3 48L32 384c-17.7 0-32 14.3-32 32zm128 0a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM320 256a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm32-80c-32.8 0-61 19.7-73.3 48L32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l246.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48l54.7 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-54.7 0c-12.3-28.3-40.5-48-73.3-48zM192 128a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm73.3-64C253 35.7 224.8 16 192 16s-61 19.7-73.3 48L32 64C14.3 64 0 78.3 0 96s14.3 32 32 32l86.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 128c17.7 0 32-14.3 32-32s-14.3-32-32-32l-214.7 0z"/></svg>',
327    extend: '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
327<path fill="currentColor" d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 .1 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 35 19.5 40.9s35-2.9 40.9-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.7c5.9-16.7-2.9-35-19.5-40.9s-35 2.9-40.9 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>'
328  };
329  var STORIES = [ {
330    id: 'install',
331    title: 'Как установить VPN',
332    label: 'Как установить VPN',
333    tile: {
334      gradient: 'linear-gradient(135deg, #1d2025 0%, #1e3a8a 55%, #40a7e3 100%)',
335      icon: FA_ICONS.install
336    },
337    steps: [ {
338      theme: 'blue',
339      title: 'Это ваш ключ',
340      body: 'Каждый ключ это отдельная карточка. Чтобы установить ключ, нажмите <b>«Мой ключ»</b>.',
341      mockup: [ 'keyCard', {
342        highlight: 'manage'
343      } ]
344    }, {
345      theme: 'violet',
346      title: 'Выберите приложение',
347      body: 'Выберите из списка рекомендованное приложение или альтернативное, если у вас проблемы с рекомендованным.',
348      mockup: [ 'appsGrid' ]
349    }, {
350      theme: 'green',
351      title: 'Установите приложение',
352      body: 'Нажмите на иконку приложения и на кнопку <b>«Установить»</b>.',
353      mockup: [ 'installFlow' ]
354    }, {
355      theme: 'teal',
356      title: 'Добавьте ключ',
357      body: 'Нажмите на иконку приложения и <b>«Добавить ключ»</b> или сделайте это вручную.',
358      mockup: [ 'addKeyFlow' ]
359    } ]
360  }, {
361    id: 'tariff',
362    title: 'Сменить тариф ключа',
363    label: 'Как сменить тариф',
364    tile: {
365      gradient: 'linear-gradient(135deg, #1d2025 0%, #4c1d95 55%, #8b5cf6 100%)',
366      icon: FA_ICONS.tariff
367    },
368    steps: [ {
369      theme: 'violet',
370      title: 'Откройте «Мой ключ»',
371      body: 'Тариф меняется через карточку ключа. Сначала откройте <b>«Мой ключ»</b>.',
372      mockup: [ 'keyCard', {
373        highlight: 'manage'
374      } ]
375    }, {
376      theme: 'magenta',
377      title: 'Перейдите в смену тарифа',
378      body: 'Внутри карточки переключите вкладку с <b>«Управление»</b> на <b>«Смена тарифа»</b>.',
379      mockup: [ 'manageToggle', {
380        tab: 'change'
381      } ]
382    }, {
383      theme: 'magenta',
384      title: 'Поменяйте параметры',
385      body: 'Двигайте бегунок устройств, включайте доп. опции и нажмите <b>«Изменить тариф»</b>.',
386      mockup: [ 'tariffChangeUI' ],
387      duration: 7e3
388    } ]
389  }, {
390    id: 'extend',
391    title: 'Продлить ключ',
392    label: 'Как продлить ключ',
393    tile: {
394      gradient: 'linear-gradient(135deg, #1d2025 0%, #92400e 55%, #f59e0b 100%)',
395      icon: FA_ICONS.extend
396    },
397    steps: [ {
398      theme: 'amber',
399      title: 'Кнопка «Продлить»',
400      body: 'Кнопка <b>«Продлить»</b> всегда доступна - просто нажмите её. Ключ остаётся прежним, переустанавливать ничего не нужно.',
401      mockup: [ 'keyCard', {
402        highlight: 'renew'
403      } ]
404    }, {
405      theme: 'amber',
406      title: 'Выберите срок',
407      body: 'Переключайте срок 1 / 3 / 6 / 12 месяцев - цена пересчитается сразу. Когда всё ок, жмите <b>«Продлить»</b>.',
408      mockup: [ 'renewUI' ]
409    }, {
410      theme: 'green',
411      title: 'Вы успешно продлили ключ',
412      body: 'Сумма списалась с баланса AID, срок ключа продлён. Можно пользоваться дальше.',
413      mockup: [ 'renewSuccess' ]
414    } ]
415  } ];
416  function register() {
417    if (window.AidVpnStories && typeof window.AidVpnStories.configure === 'function') {
418      window.AidVpnStories.configure({
419        stories: STORIES,
420        mockups: MOCKUPS,
421        themes: THEMES
422      });
423    } else {
424      window.setTimeout(register, 50);
425    }
426  }
427  if (document.readyState === 'loading') {
428    document.addEventListener('DOMContentLoaded', register);
429  } else {
430    register();
431  }
432})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.