PageSourceSearch

https://www.antiqueauction.ru/firebase-init.js?v=1.0.1790963138

js antiqueauction.ru collected 2026-10-02 17:46:16 UTC 30,077 bytes, 742 lines download raw bytes

1// Firebase initialization and Push notifications
2// PWA и Push промпты отображаются ТОЛЬКО на мобильных устройствах (ширина <= 768px)
3// На десктопе промпты скрыты, но push уведомления все равно работают если пользователь даст разрешение
4
5import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js';
6import { getMessaging, getToken, onMessage } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging.js';
7
8// Firebase configuration
9const firebaseConfig = {
10  apiKey: "AIzaSyArheUgsyG77li-6ckmCjE3g4TrbhQdytY",
11  authDomain: "antique-auction.firebaseapp.com",
12  projectId: "antique-auction",
13  storageBucket: "antique-auction.firebasestorage.app",
14  messagingSenderId: "418020751608",
15  appId: "1:418020751608:web:63ef350c59e0f17997e30d",
16  measurementId: "G-Y6YCBES7HF"
17};
18
19// VAPID Key
20const VAPID_KEY = "BN47KhW0kBj8O-hZLPCXclO4F6Y38Y9OTDRKcpxyZTAiLbC7dd-Llo1zuUNCSdXH5FVsx3bmseUUT2XbTZRoPic";
21
22// Initialize Firebase
23const app = initializeApp(firebaseConfig);
24const messaging = getMessaging(app);
25
26// Функция проверки мобильного устройства
27function isMobileDevice() {
28  const userAgent = navigator.userAgent || navigator.vendor || window.opera;
29  
30  // Проверка ширины экрана
31  const isMobileWidth = window.innerWidth <= 768;
32  
33  // Проверка User Agent
34  const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent.toLowerCase());
35  
36  // Проверка touch events
37  const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
38  
39  return isMobileWidth || (isMobileUA && hasTouch);
40}
41
42// Функция проверки iOS устройства
43function isIOSDevice() {
44  const userAgent = navigator.userAgent || navigator.vendor || window.opera;
45  const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
46  const isIPadOS = navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
47  return isIOS || isIPadOS;
48}
49
50// Функция проверки установленного PWA на iOS
51function isIOSPWAInstalled() {
52  if (window.navigator.standalone === true) return true;
53  if (isIOSDevice() && window.matchMedia('(display-mode: standalone)').matches) return true;
54  if (localStorage.getItem('pwa-install-started') === 'true') return true;
55  return false;
56}
57
58// Общая функция проверки PWA режима
59function isPWAMode() {
60  if (window.navigator.standalone === true) return true;
61  if (window.matchMedia('(display-mode: standalone)').matches) return true;
62  return false;
63}
64
65// Показываем баннер установки PWA
66let deferredPrompt;
67let installPromptShown = false;
68let iosPromptShown = false;
69
70// Android/Chrome - используем beforeinstallprompt
71window.addEventListener('beforeinstallprompt', (e) => {
72  e.preventDefault();
73  deferredPrompt = e;
74  
75  if (isPWAMode()) {
76    console.log('Already in PWA mode, skipping install prompt');
77    return;
78  }
79  
80  if (!installPromptShown && isMobileDevice()) {
81    setTimeout(() => {
82      showInstallPrompt();
83    }, 5000);
84  }
85});
86
87// iOS - показываем инструкцию
88if (isIOSDevice() && !isIOSPWAInstalled() && !iosPromptShown && !isPWAMode()) {
89  setTimeout(() => {
90    showIOSInstallInstructions();
91    iosPromptShown = true;
92  }, 5000);
93}
94
95function showInstallPrompt() {
96  const installBanner = document.createElement('div');
97  installBanner.id = 'pwa-install-banner';
98  installBanner.className = 'install-app-cto-panel';
99  installBanner.style.cssText = `
100    background: #252930;
101    position: fixed;
102    left: 0;
103    right: 0;
104    bottom: 0;
105    z-index: 9999;
106    padding: 20px;
107    display: flex;
108    justify-content: center;
109    align-items: center;
110    gap: 8px;
111    box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
112    font-family: Arial, sans-serif;
113  `;
114  
115  installBanner.innerHTML = `
116    <button id="pwa-dismiss-btn" style="border: none; background: transparent; padding: 0; cursor: pointer; flex-shrink: 0;">
117      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#e74b3d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
118        <line x1="18" y1="6" x2="6" y2="18"></line>
119        <line x1="6" y1="6" x2="18" y2="18"></line>
120      </svg>
121    </button>
122    
123    <button id="pwa-install-btn" style="
124      border: 2px solid white;
125      padding: 10px 12px;
126      flex-grow: 1;
127      max-width: 300px;
128      box-sizing: border-box;
129      color: white;
130      background: transparent;
131      display: flex;
132      justify-content: center;
133      align-items: center;
134      font-size: 14px;
135      font-weight: bold;
136      border-radius: 12px;
137      text-align: center;
138      cursor: pointer;
139      transition: all 0.2s;
140    " onmouseover="this.style.background='white'; this.style.color='black';" onmouseout="this.style.background='transparent'; this.style.color='white';">
141      Установить приложение
142    </button>
143    
144    <div style="flex-shrink: 0;">
145      <img src="/img/icon-192x192.png" alt="Install PWA" style="width: 60px; height: 60px; border-radius: 8px;">
146    </div>
147  `;
148  
149  document.body.appendChild(installBanner);
150  installPromptShown = true;
151
152  // Установка приложения
153  document.getElementById('pwa-install-btn').addEventListener('click', async () => {
154    localStorage.setItem('pwa-install-started', 'true');
155    
156    if (deferredPrompt) {
157      deferredPrompt.prompt();
158      const { outcome } = await deferredPrompt.userChoice;
159      console.log(`User response: ${outcome}`);
160      deferredPrompt = null;
161    }
162    installBanner.remove();
163  });
164
165  // Закрытие баннера
166  document.getElementById('pwa-dismiss-btn').addEventListener('click', () => {
167    installBanner.remove();
168  });
169}
170
171// iOS инструкции по установке PWA (плашка как на art-critic.ru)
172function showIOSInstallInstructions() {
173  if (isIOSPWAInstalled()) {
174    console.log('PWA уже установлено на iOS');
175    return;
176  }
177
178  if (localStorage.getItem('ios-install-dismissed')) {
179    return;
180  }
181
182  const iosPanel = document.createElement('div');
183  iosPanel.id = 'ios-install-panel';
184  iosPanel.className = 'install-app-cto-panel';
185  iosPanel.style.cssText = `
186    background: #252930;
187    position: fixed;
188    left: 0;
189    right: 0;
190    bottom: 0;
191    z-index: 9999;
192    padding: 20px;
193    display: flex;
194    justify-content: center;
195    align-items: center;
196    gap: 8px;
197    box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
198    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
199  `;
200  
201  iosPanel.innerHTML = `
202    <button id="ios-close-panel" style="border: none; background: transparent; padding: 0; cursor: pointer; flex-shrink: 0;">
203      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#e74b3d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
204        <line x1="18" y1="6" x2="6" y2="18"></line>
205        <line x1="6" y1="6" x2="18" y2="18"></line>
206      </svg>
207    </button>
208    
209    <button id="ios-install-link" style="
210      border: 2px solid white;
211      padding: 10px 12px;
212      flex-grow: 1;
213      max-width: 300px;
214      box-sizing: border-box;
215      color: white;
216      background: transparent;
217      display: flex;
218      justify-content: center;
219      align-items: center;
220      font-size: 14px;
221      font-weight: bold;
222      border-radius: 12px;
223      text-align: center;
224      cursor: pointer;
225      transition: all 0.2s;
226    " onmouseover="this.style.background='white'; this.style.color='black';" onmouseout="this.style.background='transparent'; this.style.color='white';">
227      Установить приложение
228    </button>
229    
230    <div style="flex-shrink: 0;">
231      <img src="/img/icon-192x192.png" alt="Install PWA" style="width: 60px; height: 60px; border-radius: 8px;">
232    </div>
233  `;
234  
235  document.body.appendChild(iosPanel);
236
237  // Обработчик закрытия
238  document.getElementById('ios-close-panel').addEventListener('click', () => {
239    iosPanel.remove();
240    localStorage.setItem('ios-install-dismissed', 'true');
241  });
242
243  // Обработчик клика на кнопку установки - показываем инструкцию
244  document.getElementById('ios-install-link').addEventListener('click', () => {
245    localStorage.setItem('pwa-install-started', 'true');
246    showIOSInstallModal();
247  });
248
249  console.log('iOS install panel shown');
250}
251
252// Модальное окно с инструкцией для iOS
253function showIOSInstallModal() {
254  const modal = document.createElement('div');
255  modal.id = 'ios-install-modal';
256  modal.style.cssText = `
257    position: fixed;
258    top: 0;
259    left: 0;
260    right: 0;
261    bottom: 0;
262    background: rgba(0, 0, 0, 0.85);
263    z-index: 10000;
264    display: flex;
265    align-items: center;
266    justify-content: center;
267    padding: 20px;
268    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
269    overflow-y: auto;
270  `;
271  
272  modal.innerHTML = `
273    <div style="background: white; border-radius: 16px; max-width: 500px; width: 100%; max-height: 90vh; overflow-y: auto; position: relative;">
274      <button id="modal-close-btn" style="position: sticky; top: 0; right: 0; float: right; background: none; border: none; font-size: 32px; color: #999; cursor: pointer; padding: 12px 16px; z-index: 1;">&times;</button>
275      
276      <div style="padding: 20px 24px 24px;">
277        <div style="text-align: center; margin-bottom: 24px; clear: both;">
278          <div style="width: 80px; height: 80px; background: #32363a; border-radius: 16px; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; overflow: hidden;">
279            <img src="/img/icon-192x192.png" alt="ART 1" style="width: 100%; height: 100%; object-fit: cover;">
280          </div>
281          <h2 style="margin: 0 0 4px 0; font-size: 18px; color: #333; font-weight: 600;">ART 1</h2>
282          <p style="margin: 0; font-size: 14px; color: #666;">Установите приложение на главный экран</p>
283        </div>
284
285        <div style="background: #f8f9fa; border-radius: 12px; padding: 20px; margin-bottom: 20px;">
286          <h3 style="margin: 0 0 16px 0; font-size: 16px; color: #333; font-weight: 600;">Как установить приложение:</h3>
287          
288          <ol style="margin: 0; padding: 0; list-style: none; counter-reset: step-counter;">
289            <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;">
290              <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">1</span>
291              Откройте приложение <strong>Safari</strong> 
292              <img src="https://cdn-icons-png.flaticon.com/512/564/564442.png" alt="Safari" style="width: 20px; height: 20px; vertical-align: middle; margin: 0 2px;">
293              на устройстве iPhone или iPad.
294            </li>
295            
296            <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;">
297              <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">2</span>
298              Перейдите на сайт, для которого вы хотите создать ярлык.
299            </li>
300            
301            <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;">
302              <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">3</span>
303              В нижней панели Safari нажмите на значок «Поделиться» 
304              <img src="https://cdn-icons-png.flaticon.com/512/10495/10495796.png" alt="Поделиться" style="width: 20px; height: 20px; vertical-align: middle; margin: 0 2px;">
305            </li>
306            
307            <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;">
308              <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">4</span>
309              Выберите <strong>«Добавить на главный экран»</strong>
310              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin: 0 2px;">
311                <rect x="3" y="3" width="18" height="18" rx="2"></rect>
312                <path d="M12 8v8M8 12h8"></path>
313              </svg>
314            </li>
315            
316            <li style="counter-increment: step-counter; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;">
317              <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">5</span>
318              Измените или подтвердите сведения о сайте и нажмите <strong>«Добавить»</strong>.
319            </li>
320          </ol>
321        </div>
322
323        <div style="background: #e8f5e9; border: 1px solid #4caf50; border-radius: 8px; padding: 12px; margin-bottom: 20px;">
324          <p style="margin: 0; font-size: 13px; color: #2e7d32; line-height: 1.5;">
325            <strong>✓ Готово!</strong> На главный экран добавлен ярлык для быстрого доступа к сайту.
326          </p>
327        </div>
328
329        <div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 12px;">
330          <p style="margin: 0; font-size: 13px; color: #856404; line-height: 1.5;">
331            <strong>💡 Важно:</strong> Push-уведомления доступны только после установки приложения на главный экран (iOS 16.4+).
332          </p>
333        </div>
334      </div>
335    </div>
336  `;
337  
338  document.body.appendChild(modal);
339
340  // Закрытие модалки
341  const closeModal = () => modal.remove();
342  
343  document.getElementById('modal-close-btn').addEventListener('click', closeModal);
344  
345  // Закрытие по клику на фон
346  modal.addEventListener('click', (e) => {
347    if (e.target === modal) {
348      closeModal();
349    }
350  });
351
352  console.log('iOS install modal shown');
353}
354
355// Регистрируем Service Worker при загрузке
356if ('serviceWorker' in navigator) {
357  navigator.serviceWorker.register('/firebase-messaging-sw.js')
358    .then(async registration => {
359      console.log('Firebase SW registered:', registration.scope);
360      
361      // Ждем активации Service Worker
362      if (registration.installing) {
363        console.log('Service Worker installing, waiting for activation...');
364        await waitForServiceWorkerActivation(registration);
365      } else if (registration.waiting) {
366        console.log('Service Worker waiting, activating...');
367        await waitForServiceWorkerActivation(registration);
368      } else if (registration.active) {
369        console.log('Service Worker already active');
370      }
371    })
372    .catch(err => {
373      console.error('SW registration failed:', err);
374    });
375}
376
377// Функция ожидания активации Service Worker
378function waitForServiceWorkerActivation(registration) {
379  return new Promise((resolve) => {
380    const worker = registration.installing || registration.waiting || registration.active;
381    
382    if (!worker) {
383      resolve();
384      return;
385    }
386    
387    if (worker.state === 'activated') {
388      resolve();
389      return;
390    }
391    
392    worker.addEventListener('statechange', (e) => {
393      console.log('Service Worker state changed to:', e.target.state);
394      if (e.target.state === 'activated') {
395        resolve();
396      }
397    });
398  });
399}
400
401// Отслеживание успешной установки
402window.addEventListener('appinstalled', async (evt) => {
403  console.log('PWA успешно установлено!');
404  
405  // Сохраняем флаг установки
406  localStorage.setItem('pwa-install-started', 'true');
407  localStorage.setItem('pwa-installed', 'true');
408  
409  // Скрываем все баннеры установки
410  const installBanner = document.getElementById('pwa-install-banner');
411  const iosPanel = document.getElementById('ios-install-panel');
412  if (installBanner) installBanner.remove();
413  if (iosPanel) iosPanel.remove();
414  
415  // ПОКАЗЫВАЕМ УВЕДОМЛЕНИЕ ОБ УСПЕШНОЙ УСТАНОВКЕ
416  showInstallSuccessNotification();
417  
418  // Автоматически запрашиваем разрешение на push после установки
419  setTimeout(() => {
420    requestNotificationPermission();
421  }, 3000);
422});
423
424// Функция показа уведомления об успешной установке
425async function showInstallSuccessNotification() {
426  // Способ 1: Через Service Worker (если есть разрешение)
427  if ('serviceWorker' in navigator && Notification.permission === 'granted') {
428    try {
429      const registration = await navigator.serviceWorker.getRegistration();
430      if (registration) {
431        await registration.showNotification('ART 1', {
432          body: 'Приложение успешно установлено! Теперь вы можете получать уведомления о новых лотах и торгах.',
433          icon: '/img/icon-192x192.png',
434          badge: '/img/icon-96x96.png',
435          tag: 'install-success',
436          vibrate: [200, 100, 200],
437          requireInteraction: false
438        });
439        console.log('Install success notification shown via SW');
440        return;
441      }
442    } catch (e) {
443      console.log('SW notification failed:', e);
444    }
445  }
446  
447  // Способ 2: Через Notification API напрямую
448  if ('Notification' in window && Notification.permission === 'granted') {
449    try {
450      new Notification('ART 1', {
451        body: 'Приложение успешно установлено!',
452        icon: '/img/icon-192x192.png',
453        tag: 'install-success'
454      });
455      console.log('Install success notification shown via Notification API');
456      return;
457    } catch (e) {
458      console.log('Notification API failed:', e);
459    }
460  }
461  
462  // Способ 3: Показываем красивый баннер внутри страницы (fallback)
463  showInstallSuccessBanner();
464}
465
466// Красивый баннер об успешной установке (если уведомления не доступны)
467function showInstallSuccessBanner() {
468  const banner = document.createElement('div');
469  banner.id = 'install-success-banner';
470  banner.style.cssText = `
471    position: fixed;
472    top: 20px;
473    left: 50%;
474    transform: translateX(-50%);
475    background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
476    color: white;
477    padding: 16px 24px;
478    border-radius: 12px;
479    box-shadow: 0 4px 20px rgba(0,0,0,0.3);
480    z-index: 10001;
481    display: flex;
482    align-items: center;
483    gap: 12px;
484    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
485    animation: slideDown 0.3s ease;
486    max-width: 90%;
487  `;
488  
489  banner.innerHTML = `
490    <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
491      <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
492      <polyline points="22 4 12 14.01 9 11.01"/>
493    </svg>
494    <div>
495      <div style="font-weight: 600; font-size: 16px;">Приложение установлено!</div>
496      <div style="font-size: 13px; opacity: 0.9;">Теперь доступно на главном экране</div>
497    </div>
498  `;
499  
500  // Добавляем анимацию
501  const style = document.createElement('style');
502  style.textContent = `
503    @keyframes slideDown {
504      from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
505      to { opacity: 1; transform: translateX(-50%) translateY(0); }
506    }
507  `;
508  document.head.appendChild(style);
509  
510  document.body.appendChild(banner);
511  
512  // Убираем через 5 секунд
513  setTimeout(() => {
514    banner.style.animation = 'slideDown 0.3s ease reverse';
515    setTimeout(() => banner.remove(), 300);
516  }, 5000);
517  
518  console.log('Install success banner shown');
519}
520
521// Запрос разрешения на уведомления
522async function requestNotificationPermission() {
523  try {
524    const permission = await Notification.requestPermission();
525    
526    if (permission === 'granted') {
527      console.log('Notification permission granted.');
528      await subscribeToNotifications();
529    } else {
530      console.log('Notification permission denied.');
531    }
532  } catch (error) {
533    console.error('Error requesting notification permission:', error);
534  }
535}
536
537// Подписка на push уведомления
538async function subscribeToNotifications() {
539  try {
540    console.log('Starting subscription process...');
541    
542    // Получаем зарегистрированный Service Worker для Firebase
543    const registration = await navigator.serviceWorker.getRegistration('/');
544    if (!registration) {
545      console.error('No service worker registered!');
546      return;
547    }
548    
549    // Проверяем что Service Worker активен
550    if (!registration.active) {
551      console.log('Service Worker not active, waiting for activation...');
552      await waitForServiceWorkerActivation(registration);
553    }
554    
555    console.log('✓ Using Service Worker registration:', registration.scope);
556    console.log('✓ Service Worker state:', registration.active?.state);
557
558    // Получаем FCM токен
559    console.log('Getting FCM token...');
560    const currentToken = await getToken(messaging, {
561      vapidKey: VAPID_KEY,
562      serviceWorkerRegistration: registration
563    });
564
565    if (currentToken) {
566      console.log('✓ FCM Token received:', currentToken);
567      // Отправляем токен на сервер для сохранения
568      await saveTokenToServer(currentToken);
569      console.log('✓ Subscription complete!');
570    } else {
571      console.log('No registration token available.');
572    }
573  } catch (error) {
574    console.error('❌ Error subscribing to notifications:', error);
575  }
576}
577
578// Сохранение токена на сервере
579async function saveTokenToServer(token) {
580  try {
581    const response = await fetch('/wp-admin/admin-ajax.php', {
582      method: 'POST',
583      headers: {
584        'Content-Type': 'application/x-www-form-urlencoded',
585      },
586      body: new URLSearchParams({
587        action: 'save_fcm_token',
588        token: token,
589        user_agent: navigator.userAgent,
590        subscribed_at: new Date().toISOString()
591      })
592    });
593
594    const data = await response.json();
595    console.log('Token saved to server:', data);
596  } catch (error) {
597    console.error('Error saving token to server:', error);
598  }
599}
600
601// Обработка входящих сообщений (когда приложение открыто)
602onMessage(messaging, async (payload) => {
603  console.log('Message received in foreground:', payload);
604  
605  const notificationTitle = payload.notification?.title || 'ART 1';
606  const notificationOptions = {
607    body: payload.notification?.body || 'Новое уведомление',
608    icon: payload.notification?.icon || '/img/icon-192x192.png',
609    badge: '/img/icon-96x96.png',
610    tag: 'antique-auction-foreground',
611    requireInteraction: false,
612    data: {
613      url: payload.data?.url || payload.fcmOptions?.link || '/'
614    }
615  };
616
617  // Показываем уведомление через Service Worker (работает на Android)
618  if (Notification.permission === 'granted') {
619    try {
620      const registration = await navigator.serviceWorker.getRegistration();
621      if (registration) {
622        await registration.showNotification(notificationTitle, notificationOptions);
623        console.log('Notification shown via Service Worker');
624      }
625    } catch (error) {
626      console.error('Error showing notification:', error);
627    }
628  }
629});
630
631// Флаг готовности Service Worker
632let serviceWorkerReady = false;
633
634// Автоматическая подписка при загрузке (ТОЛЬКО после готовности SW)
635window.addEventListener('load', async () => {
636  if ('Notification' in window) {
637    // ЖДЕМ регистрации Service Worker
638    console.log('Waiting for Service Worker to be ready...');
639    await navigator.serviceWorker.ready;
640    serviceWorkerReady = true;
641    console.log('Service Worker is ready, checking permissions...');
642    
643    if (Notification.permission === 'granted') {
644      // Уже есть разрешение - подписываемся
645      console.log('Permission already granted, subscribing...');
646      subscribeToNotifications();
647    } else if (Notification.permission === 'default' && isMobileDevice()) {
648      // Показываем промпт через 10 секунд ТОЛЬКО на мобильных
649      setTimeout(() => {
650        showNotificationPrompt();
651      }, 10000);
652    }
653  }
654});
655
656// Проверка PWA режима (ПОСЛЕ загрузки)
657window.addEventListener('load', async () => {
658  if (window.matchMedia('(display-mode: standalone)').matches) {
659    console.log('Running as PWA, waiting for SW...');
660    await navigator.serviceWorker.ready;
661    serviceWorkerReady = true;
662    console.log('SW ready in PWA mode');
663    
664    if (Notification.permission === 'granted') {
665      subscribeToNotifications();
666    } else if (Notification.permission === 'default' && isMobileDevice()) {
667      // В PWA сразу запрашиваем разрешение ТОЛЬКО на мобильных
668      setTimeout(() => {
669        requestNotificationPermission();
670      }, 2000);
671    }
672  }
673});
674
675function showNotificationPrompt() {
676  const promptBanner = document.createElement('div');
677  promptBanner.id = 'notification-prompt-banner';
678  promptBanner.className = 'notification-cto-panel';
679  promptBanner.style.cssText = `
680    background: #252930;
681    position: fixed;
682    left: 0;
683    right: 0;
684    bottom: 0;
685    z-index: 9999;
686    padding: 20px;
687    display: flex;
688    justify-content: center;
689    align-items: center;
690    gap: 8px;
691    box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
692    font-family: Arial, sans-serif;
693  `;
694  
695  promptBanner.innerHTML = `
696    <button id="notification-close-btn" style="border: none; background: transparent; padding: 0; cursor: pointer; flex-shrink: 0;">
697      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#e74b3d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
698        <line x1="18" y1="6" x2="6" y2="18"></line>
699        <line x1="6" y1="6" x2="18" y2="18"></line>
700      </svg>
701    </button>
702    
703    <button id="notification-allow-btn" style="
704      border: 2px solid white;
705      padding: 10px 12px;
706      flex-grow: 1;
707      max-width: 300px;
708      box-sizing: border-box;
709      color: white;
710      background: transparent;
711      display: flex;
712      justify-content: center;
713      align-items: center;
714      font-size: 14px;
715      font-weight: bold;
716      border-radius: 12px;
717      text-align: center;
718      cursor: pointer;
719      transition: all 0.2s;
720    " onmouseover="this.style.background='white'; this.style.color='black';" onmouseout="this.style.background='transparent'; this.style.color='white';">
721      Разрешить уведомления
722    </button>
723    
724    <div style="flex-shrink: 0; width: 60px; height: 60px; background: #8b0000; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 28px;">
725      🔔
726    </div>
727  `;
728  
729  document.body.appendChild(promptBanner);
730
731  document.getElementById('notification-allow-btn').addEventListener('click', () => {
732    requestNotificationPermission();
733    promptBanner.remove();
734  });
735
736  document.getElementById('notification-close-btn').addEventListener('click', () => {
737    promptBanner.remove();
738  });
739}
740
741console.log('Firebase initialized successfully!');
742

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.