PageSourceSearch

https://spbtech.ru/members/login

html spbtech.ru collected 2026-10-09 21:35:37 UTC 17,624 bytes, 545 lines download raw bytes

1<!DOCTYPE html><html lang="ru" data-multify-language="ru" data-multify-region="RU"><head><meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="robots" content="noindex"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>‎</title><!--favicons--> <link rel="icon" type="image/png" sizes="32x32" href="https://static.tildacdn.com/tild3333-3736-4639-b538-376461323536/Coat_of_arms_of_Sain.png" media="(prefers-color-scheme: light)"> <link rel="icon" type="image/png" sizes="32x32" href="https://static.tildacdn.com/tild3066-6635-4938-b934-633339333539/Coat_of_arms_of_Sain.png" media="(prefers-color-scheme: dark)"> <link rel="icon" type="image/svg+xml" sizes="any" href="https://static.tildacdn.com/tild3532-6563-4362-b234-613237373832/Coat_of_arms_of_Sain.svg"> <link rel="apple-touch-icon" type="image/png" href="https://static.tildacdn.com/tild3163-6138-4561-b065-383061623238/Frame_354.png"> <link rel="icon" type="image/png" sizes="192x192" href="https://static.tildacdn.com/tild3163-6138-4561-b065-383061623238/Frame_354.png"> <!--/favicons--><link href="https://members.tildaapi.com/frontend/css/tilda-members-base.min.css?t=1791466972" rel="stylesheet">
1<script src="https://spbtech.ru/proxy-5c1448ff47?url=https%3A%2F%2Fmembers.tildaapi.com%2Ffrontend%2Fjs%2Ftilda-members-init.min.js%3Ft%3D1791466972" id="tilda-membersarea-js"></script>
1<link rel="alternate" hreflang="x-default" href="https://spbtech.ru/members/login"><link rel="alternate" hreflang="ru-RU" href="https://spbtech.ru/members/login"><link rel="alternate" hreflang="ru" href="https://spbtech.ru/members/login"><link rel="alternate" hreflang="en-US" href="https://spbtech.ru/en/members/login"><link rel="alternate" hreflang="en" href="https://spbtech.ru/en/members/login"><link rel="alternate" hreflang="zh-CN" href="https://spbtech.ru/cn/members/login"><link rel="alternate" hreflang="zh" href="https://spbtech.ru/cn/members/login"></head><body><div id="allrecords" class="t-records" data-tilda-project-id="12180255" data-tilda-root-zone="com" data-tilda-project-country="RU"></div>
1<script>window.multify = {
2  locale: 'ru_RU',
3  language: 'RU',
4  region: 'RU',
5  prefix: '',
6  root: 'spbtech.ru',
7  variants: [{"locale":"en_US","language":"EN","region":"US","prefix":"/en","root":"spbtech.ru/en"},{"locale":"zh_CN","language":"ZH","region":"CN","prefix":"/cn","root":"spbtech.ru/cn"}],
8}
9
10function watchAndProxyGlobalFunction(name, handler) {
11  function tryProxy() {
12    const original = window[name]
13    if (typeof original === 'function' && !original.__isProxied) {
14      const wrapped = function (...args) {
15        return handler.call(this, original, ...args)
16      }
17      wrapped.__isProxied = true
18      window[name] = wrapped
19      console.debug(`[Proxy] ${name} hooked`)
20    }
21  }
22
23  // Immediate attempt (e.g., inline scripts)
24  tryProxy()
25
26  // Observe dynamically added scripts
27  const observer = new MutationObserver(() => {
28    setTimeout(tryProxy, 0)
29  })
30
31  observer.observe(document.documentElement, {
32    childList: true,
33    subtree: true,
34  })
35}
36
37function proxyDictionary(dict) {
38  dict = dict || {}
39
40  const originalDict = {}
41
42  return new Proxy(originalDict, {
43    get(target, prop) {
44      // Return override value if it exists, otherwise return original
45      if (dict.hasOwnProperty(prop)) {
46        return dict[prop]
47      }
48      return target[prop]
49    },
50    set(target, prop, value) {
51      // Store the original value
52      target[prop] = value
53      return true
54    },
55    ownKeys(target) {
56      // Return all keys from both original and override
57      return [...new Set([...Object.keys(target), ...Object.keys(dict)])]
58    },
59    has(target, prop) {
60      return prop in target || prop in dict
61    },
62  })
63}
64</script>
64<script>;(function () {
65  // Generic function to check and rewrite URLs
66  function rewriteUrlIfNeeded(url) {
67    if (!url) return url
68
69    // Avoid double-wrapping already proxied URLs or generic ?url= wrappers
70    if (
71      typeof url === 'string' &&
72      (url.includes('/proxy-5c1448ff47') || /[?&]url=/.test(url))
73    ) {
74      return url
75    }
76
77    // Skip data/blob URLs
78    if (
79      typeof url === 'string' &&
80      (url.startsWith('data:') || url.startsWith('blob:'))
81    ) {
82      return url
83    }
84
85    // if (
86    //   ['tildaapi.pro', 'tildacdn.pro'].some((domain) => url.includes(domain))
87    // ) {
88    //   url = url.replace('.pro', '.com')
89    // }
90
91    // if (
92    //   ['tildaapi.one', 'tildacdn.one'].some((domain) => url.includes(domain))
93    // ) {
94    //   url = url.replace('.one', '.com')
95    // }
96
97    const excludePatterns = [
98      'geoserv.tildacdn.*/api/detect-city',
99      'geo.tildaapi.*/geo',
100      // 'feeds.tildacdn.*/api/setpostviewed',
101    ]
102
103    const matchesPattern = (url, pattern) => {
104      if (pattern.includes('*')) {
105        const regexPattern = pattern.replace(/\*/g, '.*?')
106        const regex = new RegExp(regexPattern, 'i')
107        return regex.test(url)
108      }
109      return url.includes(pattern)
110    }
111
112    if (excludePatterns.some((pattern) => matchesPattern(url, pattern))) {
113      return url
114    }
115
116    // Check if URL contains our target domain
117    if (
118      ['tildaapi', 'tildacdn', 'remarked.ru'].some((domain) =>
119        url.includes(domain),
120      )
121    ) {
122      // Create new URL with current host + /api-proxy/ + encoded original URL
123      const newUrl = `/proxy-5c1448ff47?url=${encodeURIComponent(url)}`
124      console.log(`URL rewritten: ${url} -> ${newUrl}`)
125      return newUrl
126    }
127
128    return url
129  }
130
131  // Store the original XMLHttpRequest open method
132  const originalOpen = XMLHttpRequest.prototype.open
133
134  // Override the XMLHttpRequest open method
135  XMLHttpRequest.prototype.open = function (
136    method,
137    url,
138    async = true,
139    user,
140    password,
141  ) {
142    url = rewriteUrlIfNeeded(url)
143
144    // Call the original open method with the potentially modified URL
145    return originalOpen.call(this, method, url, async, user, password)
146  }
147
148  // Store the original fetch function
149  const originalFetch = window.fetch
150
151  // Override the fetch function
152  window.fetch = function (input, init) {
153    let url = input
154
155    // Handle Request objects
156    if (input instanceof Request) {
157      url = input.url
158    }
159
160    const rewrittenUrl = rewriteUrlIfNeeded(url)
161
162    // Only modify input if URL was rewritten
163    if (rewrittenUrl !== url) {
164      // If input was a Request object, create a new one with the new URL
165      if (input instanceof Request) {
166        input = new Request(rewrittenUrl, input)
167      } else {
168        input = rewrittenUrl
169      }
170    }
171
172    // Call the original fetch with the potentially modified input
173    return originalFetch.call(this, input, init)
174  }
175
176  // Fix for timeout and responseType issues with synchronous requests
177  const originalSetTimeout = Object.getOwnPropertyDescriptor(
178    XMLHttpRequest.prototype,
179    'timeout',
180  ).set
181  const originalSetResponseType = Object.getOwnPropertyDescriptor(
182    XMLHttpRequest.prototype,
183    'responseType',
184  ).set
185
186  // Override the timeout setter
187  Object.defineProperty(XMLHttpRequest.prototype, 'timeout', {
188    set: function (value) {
189      try {
190        originalSetTimeout.call(this, value)
191      } catch (e) {
192        // Silently ignore timeout errors for synchronous requests
193        if (
194          !e.message.includes(
195            'synchronous XMLHttpRequests do not support timeout',
196          )
197        ) {
198          throw e
199        }
200      }
201    },
202    get: Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'timeout')
203      .get,
204  })
205
206  // Override the responseType setter
207  Object.defineProperty(XMLHttpRequest.prototype, 'responseType', {
208    set: function (value) {
209      try {
210        originalSetResponseType.call(this, value)
211      } catch (e) {
212        // Silently ignore responseType errors for synchronous requests
213        if (
214          !e.message.includes(
215            'synchronous XMLHttpRequests do not support responseType',
216          )
217        ) {
218          throw e
219        }
220      }
221    },
222    get: Object.getOwnPropertyDescriptor(
223      XMLHttpRequest.prototype,
224      'responseType',
225    ).get,
226  })
227})()
228</script>
228<script>;(async function () {
229  const LOCALE_KEY = 'multify_locale'
230
231  const clearCart = () => {
232    sessionStorage.removeItem('tCityFromGeo')
233    localStorage.removeItem('iso_code')
234    localStorage.removeItem('tcart')
235    localStorage.removeItem('tcart_countries')
236    localStorage.removeItem('tcart_discounts')
237  }
238
239  const clearWishlist = () => {
240    sessionStorage.removeItem('tCityFromGeo')
241    localStorage.removeItem('iso_code')
242    localStorage.removeItem('twishlist')
243  }
244
245  const validate = (options = { reload: false, updateLocale: true }) => {
246    const cartElement = document.querySelector('.t706[data-project-currency]')
247
248    if (!cartElement) {
249      return
250    }
251
252    const currency = cartElement.getAttribute('data-project-currency')
253
254    if (!currency) {
255      return
256    }
257
258    const currentLocale = window.multify?.locale
259    const storedLocale = localStorage.getItem(LOCALE_KEY)
260    const tcart = JSON.parse(localStorage.getItem('tcart'))
261    const twishlist = JSON.parse(localStorage.getItem('twishlist'))
262
263    // Update stored locale (only on initial load, not from storage events)
264    if (
265      options.updateLocale &&
266      currentLocale &&
267      currentLocale !== storedLocale
268    ) {
269      localStorage.setItem(LOCALE_KEY, currentLocale)
270    }
271
272    // Check locale mismatch (if we had a stored locale and it changed)
273    if (storedLocale && currentLocale && storedLocale !== currentLocale) {
274      console.warn(
275        `[Multify] Locale mismatch, clearing cart & wishlist (expected: ${currentLocale}, got: ${storedLocale})`,
276      )
277
278      clearCart()
279      clearWishlist()
280
281      if (options.reload && tcart) {
282        location.reload()
283      }
284      return
285    }
286
287    // Check currency mismatch (fallback for non-multify or same-locale scenarios)
288    if (tcart && currency !== tcart.currency) {
289      console.warn(
290        `[Multify] Currency mismatch, clearing cart (expected: ${currency}, got: ${tcart.currency})`,
291      )
292
293      clearCart()
294
295      if (options.reload) {
296        location.reload()
297      }
298    } else if (twishlist && currency !== twishlist.currency) {
299      console.warn(
300        `[Multify] Currency mismatch, clearing wishlist (expected: ${currency}, got: ${twishlist.currency})`,
301      )
302
303      clearWishlist()
304
305      if (options.reload) {
306        location.reload()
307      }
308    }
309  }
310
311  window.addEventListener('storage', function (e) {
312    // Ignore locale key changes to prevent infinite loop between tabs
313    if (e.storageArea === localStorage && e.key !== LOCALE_KEY) {
314      console.debug('[Multify] LocalStorage changed, validating cart')
315      validate({ reload: false, updateLocale: false })
316    }
317  })
318
319  document.addEventListener('DOMContentLoaded', () => {
320    validate({ reload: true, updateLocale: true })
321  })
322})()
323</script>
323<style>.t744__price.force-right,
324.t744__price.force-left {
325  display: inline-flex;
326  margin-right: 0 !important;
327}
328
329.t744__price.force-right .t744__price-value {
330  order: 1;
331}
332
333.t744__price.force-right .t744__price-currency {
334  order: 2;
335  margin-left: 5px;
336}
337
338t-store__prod__price-portion
339
340.t754__price,
341.t754__price_old {
342  display: inline-flex;
343}
344
345.t754__price.force-right .t754__price-value,
346.t754__price_old.force-right .t754__price-value {
347  order: 1;
348}
349
350.t754__price.force-right .t754__price-currency,
351.t754__price_old.force-right .t754__price-currency {
352  order: 2;
353}
354
355.t754__price.force-left .t754__price-value,
356.t754__price_old.force-left .t754__price-value {
357  order: 2;
358}
359
360.t754__price.force-left .t754__price-currency,
361.t754__price_old.force-left .t754__price-currency {
362  order: 1;
363}
364
365.t754__price.force-left .t754__price-currency::before,
366.t754__price_old.force-left .t754__price-currency::before,
367.js-catalog-prod-price.force-left .t-catalog__prod-popup__price-currency::before,
368.js-catalog-prod-price-old.force-left .t-catalog__prod-popup__price-currency::before {
369  display: none !important;
370}
371
372.t754__price.force-left .t754__price-currency::after,
373.t754__price_old.force-left .t754__price-currency::after,
374.js-catalog-prod-price.force-left .t-catalog__prod-popup__price-currency::after,
375.js-catalog-prod-price-old.force-left .t-catalog__prod-popup__price-currency::after {
376  content: ' ';
377  white-space: pre;
378}
379
380.js-store-prod-price.force-right,
381.js-store-prod-price.force-left,
382.js-store-prod-price-old.force-right,
383.js-store-prod-price-old.force-left,
384.js-catalog-prod-price.force-right,
385.js-catalog-prod-price.force-left {
386  display: inline-flex;
387}
388
389.js-store-prod-price.force-right .t-store__prod-popup__price-value,
390.js-store-prod-price-old.force-right .t-store__prod-popup__price-value
391.js-catalog-prod-price.force-right .js-catalog-prod-price-val,
392.js-catalog-prod-price-old.force-right .js-catalog-prod-price-old-val {
393  order: 1;
394}
395
396.js-store-prod-price.force-right .t-store__prod-popup__price-currency,
397.js-store-prod-price-old.force-right .t-store__prod-popup__price-currency,
398.js-catalog-prod-price.force-right .t-catalog__prod-popup__price-currency,
399.js-catalog-prod-price-old.force-right .t-catalog__prod-popup__price-currency {
400  order: 2;
401}
402
403.js-store-prod-price.force-right .t-store__prod__price-portion,
404.js-store-prod-price-old.force-right .t-store__prod__price-portion {
405  order: 3;
406}
407
408.js-store-prod-price.force-left .t-store__prod-popup__price-value,
409.js-store-prod-price-old.force-left .t-store__prod-popup__price-value,
410.js-catalog-prod-price.force-left .js-catalog-prod-price-val,
411.js-catalog-prod-price-old.force-left .js-catalog-prod-price-old-val {
412  order: 2;
413}
414
415.js-store-prod-price.force-left .t-store__prod-popup__price-currency,
416.js-store-prod-price-old.force-left .t-store__prod-popup__price-currency,
417.js-catalog-prod-price.force-left .t-catalog__prod-popup__price-currency,
418.js-catalog-prod-price-old.force-left .t-catalog__prod-popup__price-currency {
419  order: 1;
420}
421
422.js-store-prod-price.force-left .t-store__prod-popup__price-currency::before,
423.js-store-prod-price-old.force-left .t-store__prod-popup__price-currency::before {
424  display: none !important;
425}
426
427.t762__price.force-right,
428.t762__price.force-left,
429.t762__price_old.force-right,
430.t762__price_old.force-left {
431  display: inline-flex;
432}
433
434.t762__price.force-right .t762__price-value,
435.t762__price_old.force-right .t762__price-value {
436  order: 1;
437}
438
439.t762__price.force-right .t762__price-currency,
440.t762__price_old.force-right .t762__price-currency {
441  order: 2;
442}
443
444.t762__price.force-left .t762__price-value,
445.t762__price_old.force-left .t762__price-value {
446  order: 2;
447}
448
449.t762__price.force-left .t762__price-currency,
450.t762__price_old.force-left .t762__price-currency,
451.t762__price.force-left .js-product-price-currency,
452.t762__price_old.force-left .js-product-price-currency {
453  order: 1;
454}
455
456.t762__price.force-left .t762__price-currency::before,
457.t762__price_old.force-left .t762__price-currency::before,
458.t762__price.force-left .js-product-price-currency::before,
459.t762__price_old.force-left .js-product-price-currency::before {
460  display: none !important;
461}
462
463.t762__price.force-left .t762__price-currency::after,
464.t762__price_old.force-left .t762__price-currency::after,
465.t762__price.force-left .js-product-price-currency::after,
466.t762__price_old.force-left .js-product-price-currency::after {
467  content: ' ';
468  white-space: pre;
469}</style>
469<script>; (async function () {
470  const upstream = 'https://spbtech.mtfy.xyz'
471
472  // Check if this is a Tilda members area authentication page
473  if ([
474    '/members/login',
475    '/members/signup',
476    '/members/recover-password',
477  ].some(p => location.pathname.endsWith(p)) == false) {
478    return
479  }
480
481  function patchMembersLinks() {
482    // Strip protocol from upstream if present and use current protocol
483    const upstreamDomain = upstream.replace(/^https?:\/\//, '')
484    const protocol = window.location.protocol
485    const upstreamUrl = `${protocol}//${upstreamDomain}`
486
487    // if (!window.multify?.root) {
488    //   return
489    // }
490
491    // const base = window.location.protocol + '//' + window.multify.root
492
493    // const prefix = window.multify?.prefix || ''
494
495    // Find all links to signup and recover-password pages with the prefix
496    const links = document.querySelectorAll(
497      [
498        `a[href*="/members/signup"]`,
499        `a[href*="/members/recover-password"]`,
500      ].join(',')
501    )
502
503    links.forEach((link) => {
504      if (
505        !link.hasAttribute('href') ||
506        link.getAttribute('href').includes(upstreamUrl)
507      ) {
508        return
509      }
510
511      const href = link.getAttribute('href')
512      // Extract just the page name (signup or recover-password)
513      const page = href.split('/members/')[1]
514
515      // Update href to absolute upstream URL
516      link.setAttribute('href', `${upstreamUrl}/members/${page}`)
517
518      // Remove click event listeners by cloning the node
519      const newLink = link.cloneNode(true)
520      link.parentNode.replaceChild(newLink, link)
521
522      // Remove data-route attribute (used by Tilda's router)
523      newLink.removeAttribute('data-route')
524    })
525  }
526
527  // Patch links on initial load
528  document.addEventListener('DOMContentLoaded', () => {
529    patchMembersLinks()
530  })
531
532  // Also patch links after a delay in case they're added dynamically
533  setTimeout(patchMembersLinks, 500)
534
535  // Watch for DOM changes and patch new links
536  const observer = new MutationObserver(() => {
537    patchMembersLinks()
538  })
539
540  observer.observe(document.body, {
541    childList: true,
542    subtree: true,
543  })
544})()
545</script>
545</body></html>

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.