Guarantor.Su - Безопасные криптовалютные сделки с гарантией 100%

Безопасные крипто-сделки
с гарантией 100% возврата

Эскроу-сервис с мультисиг кошельками, холодным хранением и арбитражем для сделок от $100 до $10,000,000

AI-анализ рисков вашей сделки

75 безопасно
Мошенничество
15%
Волатильность
40%
Контрагент
65%
AI рекомендует:
  • ✅ Использовать мультисиг 3/5
  • ✅ Включить 24-часовой холд
  • ✅ Привлечь арбитра заранее
0
млрд $ объем сделок
0
тысяч пользователей
0
% успешных сделок
0
потерь средств
????
Покупатель
1.5 BTC
????
Эскроу
2/3 ⛓️ ❄️
????
Продавец
Цифровой актив
⚖️
Арбитр
SSL 256-bit SSL
ISO 27001 ISO 27001
SOC 2 SOC 2 Type II
Insurance Застраховано
Листайте вниз

Проблемы крипто-сделок
и как мы их решаем

????

Мошенничество

Критично

Проблема: 37% крипто-сделок заканчиваются обманом

$2.8B
убытков в 2023
63%
случаев не расследуются
????️

Наше решение

  • Верификация KYC/AML всех участников
  • Мультисиг кошельки 2/3 для выплат
  • Арбитражный комитет из 50+ экспертов
  • Страхование сделок через Lloyd's of London
Результат: 0 случаев мошенничества
????

Волатильность

Высокий риск

Проблема: Курс может измениться на 20% за время сделки

⚖️

Наше решение

  • Стабилизация в USDT на время сделки
  • Хеджирование позиций через фьючерсы
  • Динамическое обновление курса каждые 5 минут
  • Гарантия курса на 24 часа
Результат: ±0.5% отклонение
⚖️

Правовая неопределенность

Средний риск

Проблема: Отсутствие правовой защиты в 89% стран

????

Наше решение

  • Смарт-контракты с юридической силой
  • Международная арбитражная оговорка
  • Штат юристов в 15 юрисдикциях
  • Готовые договоры под разные типы сделок
Результат: 100% правовая защита
????

Технические риски

Высокий риск

Проблема: Взломы кошельков, ошибки транзакций, сетевые сбои

Ошибочные адреса
42%
Хакерские атаки
28%
Сетевые комиссии
65%
????

Наше решение

  • Валидация адресов в реальном времени
  • Холодное хранение 95% средств
  • Мониторинг транзакций 24/7
  • Динамические комиссии с оптимизацией
Результат: 99.9% аптайм

Рассчитайте ваши риски без эскроу

$100 $5,000 $1M
Вероятность потерь
Низкая Средняя Высокая
Минимальный риск 5-15%
Низкий риск 15-30%
Умеренный риск 30-50%
Средний риск 50-70%
Высокий риск 70-85%
Критический риск 85-95%
Размер потерь
Вероятность потерь: 72%
Средние потери: $3,600
Рекомендация: Обязательно использовать эскроу

С Guarantor.Su ваш риск снижается до 0.1%

// app.js - Главный файл приложения class GuarantorApp { constructor() { this.init(); } init() { this.initComponents(); this.initAnalytics(); this.initServiceWorker(); this.initPerformanceMonitoring(); this.initAIFeatures(); } initComponents() { // Инициализация всех компонентов this.components = { hero: new HeroSection(), problems: new ProblemsSection(), howItWorks: new HowItWorks(), targetAudience: new TargetAudience(), security: new SecuritySection(), dealTypes: new DealTypes(), dealProcess: new DealProcess(), cryptocurrencies: new Cryptocurrencies(), advantages: new Advantages(), cases: new CasesSection(), reviews: new ReviewsSection(), partners: new PartnersSection(), faq: new FAQSection(), calculator: new CalculatorSection(), team: new TeamSection(), blog: new BlogSection(), verification: new VerificationSection(), arbitration: new ArbitrationSection(), mobile: new MobileSection(), affiliate: new AffiliateSection(), legal: new LegalSection(), tech: new TechSection(), registration: new RegistrationSection(), cta: new CTASection(), footer: new FooterSection(), modals: new ModalsManager(), floating: new FloatingElements() }; } initAnalytics() { // Инициализация аналитики this.analytics = { google: this.initGoogleAnalytics(), yandex: this.initYandexMetrica(), hotjar: this.initHotjar(), amplitude: this.initAmplitude(), mixpanel: this.initMixpanel() }; } initServiceWorker() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('ServiceWorker зарегистрирован'); this.serviceWorker = registration; }) .catch(error => { console.error('Ошибка регистрации ServiceWorker:', error); }); } } initPerformanceMonitoring() { // Мониторинг Web Vitals if ('web-vitals' in window) { webVitals.getCLS(this.logCLS); webVitals.getFID(this.logFID); webVitals.getLCP(this.logLCP); webVitals.getFCP(this.logFCP); webVitals.getTTFB(this.logTTFB); } // Мониторинг ошибок window.addEventListener('error', this.handleError); window.addEventListener('unhandledrejection', this.handlePromiseRejection); // Мониторинг производительности this.performanceObserver = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { this.logPerformanceEntry(entry); }); }); this.performanceObserver.observe({ entryTypes: ['navigation', 'resource', 'paint'] }); } initAIFeatures() { // Инициализация AI функций this.ai = { riskAnalyzer: new AIRiskAnalyzer(), recommendationEngine: new AIRecommendationEngine(), chatAssistant: new AIChatAssistant(), fraudDetection: new AIFraudDetection(), personalization: new AIPersonalization() }; } // Методы логирования logCLS(metric) { console.log('CLS:', metric.value); this.sendAnalytics('web-vitals', 'CLS', metric.value); } logFID(metric) { console.log('FID:', metric.value); this.sendAnalytics('web-vitals', 'FID', metric.value); } logLCP(metric) { console.log('LCP:', metric.value); this.sendAnalytics('web-vitals', 'LCP', metric.value); } logFCP(metric) { console.log('FCP:', metric.value); this.sendAnalytics('web-vitals', 'FCP', metric.value); } logTTFB(metric) { console.log('TTFB:', metric.value); this.sendAnalytics('web-vitals', 'TTFB', metric.value); } handleError(event) { const error = { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error?.stack }; console.error('JavaScript Error:', error); this.sendAnalytics('error', 'js-error', error); } handlePromiseRejection(event) { console.error('Unhandled Promise Rejection:', event.reason); this.sendAnalytics('error', 'promise-rejection', event.reason); } logPerformanceEntry(entry) { const data = { name: entry.name, type: entry.entryType, startTime: entry.startTime, duration: entry.duration, ...entry.toJSON() }; console.log('Performance Entry:', data); // Отправка в аналитику для важных метрик if (entry.entryType === 'navigation') { this.sendAnalytics('performance', 'navigation', data); } } sendAnalytics(category, action, value) { // Отправка в Google Analytics if (typeof gtag !== 'undefined') { gtag('event', action, { event_category: category, event_label: JSON.stringify(value), value: typeof value === 'number' ? value : 1 }); } // Отправка в Yandex.Metrica if (typeof ym !== 'undefined') { ym(12345678, 'reachGoal', `${category}_${action}`); } // Отправка на сервер fetch('/api/analytics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ category, action, value, timestamp: Date.now(), userAgent: navigator.userAgent, url: window.location.href }) }); } // Методы работы с пользователем trackUserBehavior(action, data = {}) { const behavior = { action, data, timestamp: Date.now(), sessionId: this.getSessionId(), userId: this.getUserId() }; localStorage.setItem(`behavior_${Date.now()}`, JSON.stringify(behavior)); this.sendAnalytics('user-behavior', action, behavior); } getSessionId() { let sessionId = sessionStorage.getItem('sessionId'); if (!sessionId) { sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); sessionStorage.setItem('sessionId', sessionId); } return sessionId; } getUserId() { return localStorage.getItem('userId') || 'anonymous'; } // Методы работы с сделками createDeal(dealData) { return new Promise((resolve, reject) => { // Валидация данных if (!this.validateDealData(dealData)) { reject(new Error('Invalid deal data')); return; } // Отправка на сервер fetch('/api/deals/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dealData) }) .then(response => response.json()) .then(data => { if (data.success) { this.trackUserBehavior('deal-created', dealData); resolve(data); } else { reject(new Error(data.error || 'Creation failed')); } }) .catch(reject); }); } validateDealData(data) { const requiredFields = ['type', 'amount', 'currency', 'parties']; return requiredFields.every(field => data[field]); } // Методы безопасности encryptData(data, key) { // Использование Web Crypto API для шифрования return crypto.subtle.encrypt( { name: 'AES-GCM', iv: new Uint8Array(12) }, key, new TextEncoder().encode(JSON.stringify(data)) ); } generateKey() { return crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); } // Методы работы с криптовалютами async getCryptoPrices() { try { const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin,ethereum,tether'); const data = await response.json(); this.cachePrices(data); return data; } catch (error) { // Возвращаем кэшированные данные return this.getCachedPrices(); } } cachePrices(prices) { const cache = { data: prices, timestamp: Date.now() }; localStorage.setItem('cryptoPrices', JSON.stringify(cache)); } getCachedPrices() { const cached = localStorage.getItem('cryptoPrices'); if (cached) { const { data, timestamp } = JSON.parse(cached); // Используем кэш если ему меньше 5 минут if (Date.now() - timestamp < 5 * 60 * 1000) { return data; } } return null; } // Методы персонализации personalizeContent(userPreferences) { // Анализ предпочтений пользователя const preferences = userPreferences || this.getUserPreferences(); // Персонализация контента this.components.forEach(component => { if (component.personalize) { component.personalize(preferences); } }); // Персонализация рекомендаций this.ai.recommendationEngine.generateRecommendations(preferences); } getUserPreferences() { const prefs = localStorage.getItem('userPreferences'); return prefs ? JSON.parse(prefs) : { interests: [], dealTypes: [], riskTolerance: 'medium', experienceLevel: 'beginner' }; } // Методы для A/B тестирования runABTest(testName, variants) { const testId = `ab_test_${testName}`; let variant = localStorage.getItem(testId); if (!variant) { // Случайное назначение варианта variant = variants[Math.floor(Math.random() * variants.length)]; localStorage.setItem(testId, variant); } return variant; } trackABTestConversion(testName, variant) { this.sendAnalytics('ab-test', testName, { variant, converted: true, timestamp: Date.now() }); } // Методы для геймификации awardAchievement(achievementId) { const achievements = JSON.parse(localStorage.getItem('achievements') || '[]'); if (!achievements.includes(achievementId)) { achievements.push(achievementId); localStorage.setItem('achievements', JSON.stringify(achievements)); // Показать уведомление this.showAchievementNotification(achievementId); // Отправить в аналитику this.sendAnalytics('achievement', 'unlocked', achievementId); } } showAchievementNotification(achievementId) { const achievements = { 'first_deal': { title: 'Первая сделка!', description: 'Вы успешно завершили свою первую сделку', icon: '????' }, 'large_deal': { title: 'Крупная сделка', description: 'Завершена сделка на сумму более $10,000', icon: '????' }, 'quick_deal': { title: 'Скоростная сделка', description: 'Сделка завершена менее чем за 1 час', icon: '⚡' } }; const achievement = achievements[achievementId]; if (achievement) { this.showNotification(achievement); } } showNotification(data) { const notification = document.createElement('div'); notification.className = 'achievement-notification'; notification.innerHTML = `
${data.icon}

${data.title}

${data.description}

`; document.body.appendChild(notification); // Анимация появления gsap.from(notification, { x: 100, opacity: 0, duration: 0.5, ease: "back.out(1.7)" }); // Автоматическое скрытие setTimeout(() => { notification.remove(); }, 5000); // Закрытие по клику notification.querySelector('.close-notification').addEventListener('click', () => { notification.remove(); }); } // Методы для офлайн-работы setupOfflineSupport() { // Кэширование важных данных this.cacheCriticalData(); // Обработка офлайн-состояния window.addEventListener('online', this.handleOnline); window.addEventListener('offline', this.handleOffline); } handleOnline() { console.log('Приложение онлайн'); // Синхронизация данных this.syncOfflineData(); } handleOffline() { console.log('Приложение офлайн'); // Показать уведомление this.showOfflineNotification(); } syncOfflineData() { // Синхронизация данных, накопленных в офлайне const offlineActions = JSON.parse(localStorage.getItem('offlineActions') || '[]'); offlineActions.forEach(action => { this.performAction(action); }); localStorage.removeItem('offlineActions'); } // Методы для push-уведомлений requestNotificationPermission() { if ('Notification' in window && Notification.permission === 'default') { Notification.requestPermission().then(permission => { if (permission === 'granted') { this.setupPushNotifications(); } }); } } setupPushNotifications() { if ('serviceWorker' in navigator && 'PushManager' in window) { navigator.serviceWorker.ready.then(registration => { registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: this.urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY') }) .then(subscription => { this.saveSubscription(subscription); }) .catch(error => { console.error('Ошибка подписки на push:', error); }); }); } } urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } saveSubscription(subscription) { fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) }); } // Методы для темной темы initThemeSwitcher() { const theme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); this.setTheme(theme); // Слушатель изменения системной темы window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (!localStorage.getItem('theme')) { this.setTheme(e.matches ? 'dark' : 'light'); } }); } setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); } toggleTheme() { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; this.setTheme(newTheme); } // Методы для локализации initLocalization() { const lang = localStorage.getItem('language') || navigator.language.split('-')[0] || 'ru'; this.setLanguage(lang); } setLanguage(lang) { document.documentElement.lang = lang; localStorage.setItem('language', lang); // Загрузка переводов this.loadTranslations(lang); } async loadTranslations(lang) { try { const response = await fetch(`/locales/${lang}.json`); const translations = await response.json(); this.applyTranslations(translations); } catch (error) { console.error('Ошибка загрузки переводов:', error); } } applyTranslations(translations) { // Применение переводов ко всем элементам с data-i18n document.querySelectorAll('[data-i18n]').forEach(element => { const key = element.getAttribute('data-i18n'); if (translations[key]) { element.textContent = translations[key]; } }); } // Методы для доступности (a11y) initAccessibility() { // Управление фокусом this.setupFocusManagement(); // Поддержка клавиатуры this.setupKeyboardNavigation(); // ARIA атрибуты this.setupARIA(); // Увеличение текста this.setupTextResizing(); } setupFocusManagement() { // Сохранение фокуса в модальных окнах document.addEventListener('keydown', (e) => { if (e.key === 'Tab' && this.activeModal) { this.trapFocus(this.activeModal, e); } }); } trapFocus(element, event) { const focusable = element.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (focusable.length === 0) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey) { if (document.activeElement === first) { last.focus(); event.preventDefault(); } } else { if (document.activeElement === last) { first.focus(); event.preventDefault(); } } } setupKeyboardNavigation() { document.addEventListener('keydown', (e) => { // Закрытие модалок по ESC if (e.key === 'Escape' && this.activeModal) { this.closeModal(this.activeModal); } // Навигация по табам if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { this.handleTabNavigation(e); } }); } setupARIA() { // Динамическое обновление ARIA атрибутов this.updateLiveRegions(); // Управление состоянием элементов this.setupARIAStates(); } setupTextResizing() { // Кнопки увеличения/уменьшения текста const textSizeControls = document.createElement('div'); textSizeControls.className = 'text-size-controls'; textSizeControls.innerHTML = ` `; document.body.appendChild(textSizeControls); // Обработчики document.getElementById('increaseTextSize').addEventListener('click', () => { this.changeTextSize(1); }); document.getElementById('decreaseTextSize').addEventListener('click', () => { this.changeTextSize(-1); }); document.getElementById('resetTextSize').addEventListener('click', () => { this.resetTextSize(); }); } changeTextSize(delta) { const currentSize = parseFloat(getComputedStyle(document.documentElement).fontSize); const newSize = currentSize + delta; document.documentElement.style.fontSize = `${newSize}px`; localStorage.setItem('textSize', newSize); } resetTextSize() { document.documentElement.style.fontSize = ''; localStorage.removeItem('textSize'); } // Метод запуска приложения start() { console.log('Guarantor.Su приложение запущено'); // Запуск всех систем this.setupOfflineSupport(); this.requestNotificationPermission(); this.initThemeSwitcher(); this.initLocalization(); this.initAccessibility(); // Отслеживание производительности this.trackPerformance(); // Запуск AI систем this.ai.riskAnalyzer.start(); this.ai.recommendationEngine.start(); // Инициализация пользовательской сессии this.initUserSession(); } initUserSession() { const sessionData = { startTime: Date.now(), referrer: document.referrer, landingPage: window.location.pathname, utmParams: this.getUTMParameters(), deviceInfo: this.getDeviceInfo() }; sessionStorage.setItem('sessionData', JSON.stringify(sessionData)); this.sendAnalytics('session', 'start', sessionData); } getUTMParameters() { const params = new URLSearchParams(window.location.search); const utm = {}; ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach(param => { if (params.has(param)) { utm[param] = params.get(param); } }); return utm; } getDeviceInfo() { return { userAgent: navigator.userAgent, platform: navigator.platform, language: navigator.language, screen: { width: window.screen.width, height: window.screen.height, colorDepth: window.screen.colorDepth }, connection: navigator.connection ? { effectiveType: navigator.connection.effectiveType, downlink: navigator.connection.downlink, rtt: navigator.connection.rtt } : null }; } trackPerformance() { // Отслеживание метрик производительности const perfData = performance.getEntriesByType('navigation')[0]; if (perfData) { const metrics = { dns: perfData.domainLookupEnd - perfData.domainLookupStart, tcp: perfData.connectEnd - perfData.connectStart, ssl: perfData.connectEnd - perfData.secureConnectionStart, ttfb: perfData.responseStart - perfData.requestStart, download: perfData.responseEnd - perfData.responseStart, domReady: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart, pageLoad: perfData.loadEventEnd - perfData.loadEventStart }; this.sendAnalytics('performance', 'page-load', metrics); } } // Метод для деструктора destroy() { // Очистка всех слушателей событий this.components.forEach(component => { if (component.destroy) { component.destroy(); } }); // Отключение Service Worker if (this.serviceWorker) { this.serviceWorker.unregister(); } // Очистка анимаций gsap.globalTimeline.clear(); console.log('Приложение остановлено'); } } // Экспорт приложения window.GuarantorApp = GuarantorApp; // Запуск приложения при загрузке страницы document.addEventListener('DOMContentLoaded', () => { window.app = new GuarantorApp(); window.app.start(); }); // Глобальные обработчики ошибок window.addEventListener('error', (event) => { if (window.app) { window.app.handleError(event); } }); window.addEventListener('unhandledrejection', (event) => { if (window.app) { window.app.handlePromiseRejection(event); } });
/* main.css - Основные стили проекта */ :root { /* Цветовая палитра */ --primary-color: #6a51fe; --primary-gradient: linear-gradient(90deg, #6a51fe 0%, #3a8ffe 100%); --secondary-color: #10b981; --danger-color: #ef4444; --warning-color: #f59e0b; --info-color: #3b82f6; /* Текст */ --text-primary: #1a1a2e; --text-secondary: #4b5563; --text-tertiary: #6b7280; --text-light: #ffffff; /* Фон */ --bg-primary: #ffffff; --bg-secondary: #f9f9ff; --bg-dark: #1a1a2e; --bg-gradient: linear-gradient(135deg, #0a0b1f 0%, #1a1b3a 100%); /* Границы */ --border-color: #e6e6ff; --border-radius: 12px; --border-radius-lg: 20px; --border-radius-xl: 30px; /* Тени */ --shadow-sm: 0 2px 4px rgba(0,0,0,0.05); --shadow-md: 0 4px 12px rgba(0,0,0,0.08); --shadow-lg: 0 10px 30px rgba(0,0,0,0.1); --shadow-xl: 0 20px 60px rgba(0,0,0,0.15); --shadow-primary: 0 10px 30px rgba(106, 81, 254, 0.2); /* Отступы */ --spacing-xs: 8px; --spacing-sm: 16px; --spacing-md: 24px; --spacing-lg: 32px; --spacing-xl: 48px; --spacing-xxl: 64px; /* Типографика */ --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-family-mono: 'SF Mono', 'Roboto Mono', monospace; --font-size-xs: 12px; --font-size-sm: 14px; --font-size-md: 16px; --font-size-lg: 18px; --font-size-xl: 24px; --font-size-2xl: 32px; --font-size-3xl: 48px; --font-size-4xl: 64px; --line-height-tight: 1.2; --line-height-normal: 1.5; --line-height-relaxed: 1.8; /* Анимации */ --transition-fast: 150ms ease; --transition-normal: 300ms ease; --transition-slow: 500ms ease; --transition-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); } /* Темная тема */ [data-theme="dark"] { --text-primary: #ffffff; --text-secondary: #a0a0c0; --text-tertiary: #8b8bb0; --bg-primary: #1a1a2e; --bg-secondary: #252547; --border-color: rgba(255,255,255,0.1); --shadow-md: 0 4px 12px rgba(0,0,0,0.3); --shadow-lg: 0 10px 30px rgba(0,0,0,0.4); } /* Reset и базовые стили */ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } html { font-size: 16px; scroll-behavior: smooth; -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { font-family: var(--font-family); font-size: var(--font-size-md); line-height: var(--line-height-normal); color: var(--text-primary); background: var(--bg-primary); overflow-x: hidden; } /* Типографика */ h1, h2, h3, h4, h5, h6 { font-weight: 800; line-height: var(--line-height-tight); margin-bottom: var(--spacing-md); } h1 { font-size: var(--font-size-4xl); } h2 { font-size: var(--font-size-3xl); } h3 { font-size: var(--font-size-2xl); } h4 { font-size: var(--font-size-xl); } h5 { font-size: var(--font-size-lg); } h6 { font-size: var(--font-size-md); } p { margin-bottom: var(--spacing-md); line-height: var(--line-height-relaxed); } a { color: var(--primary-color); text-decoration: none; transition: color var(--transition-fast); } a:hover { color: var(--info-color); } /* Кнопки */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-xs); padding: var(--spacing-md) var(--spacing-lg); font-family: var(--font-family); font-size: var(--font-size-md); font-weight: 600; line-height: 1; text-align: center; border: 2px solid transparent; border-radius: var(--border-radius); cursor: pointer; transition: all var(--transition-normal); user-select: none; } .btn-primary { background: var(--primary-gradient); color: white; } .btn-primary:hover { transform: translateY(-2px); box-shadow: var(--shadow-primary); } .btn-outline { background: transparent; border-color: var(--border-color); color: var(--text-primary); } .btn-outline:hover { border-color: var(--primary-color); color: var(--primary-color); } .btn-lg { padding: var(--spacing-lg) var(--spacing-xl); font-size: var(--font-size-lg); } .btn-sm { padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-sm); } /* Формы */ .form-group { margin-bottom: var(--spacing-lg); } .form-label { display: block; margin-bottom: var(--spacing-xs); font-weight: 600; color: var(--text-secondary); } .form-control { width: 100%; padding: var(--spacing-md); font-family: var(--font-family); font-size: var(--font-size-md); line-height: var(--line-height-normal); color: var(--text-primary); background: var(--bg-primary); border: 2px solid var(--border-color); border-radius: var(--border-radius); transition: border-color var(--transition-fast); } .form-control:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(106, 81, 254, 0.1); } /* Карточки */ .card { background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: var(--border-radius-lg); padding: var(--spacing-lg); box-shadow: var(--shadow-md); transition: all var(--transition-normal); } .card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); } /* Сетка */ .container { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-lg); } .row { display: flex; flex-wrap: wrap; margin: 0 calc(-1 * var(--spacing-md)); } .col { flex: 1; padding: 0 var(--spacing-md); } /* Секции */ .section { padding: var(--spacing-xxl) 0; position: relative; } .section-title { text-align: center; margin-bottom: var(--spacing-xxl); } .gradient-text { background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } /* Анимации */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes slideDown { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Утилитарные классы */ .text-center { text-align: center; } .text-right { text-align: right; } .text-left { text-align: left; } .mt-0 { margin-top: 0; } .mt-1 { margin-top: var(--spacing-xs); } .mt-2 { margin-top: var(--spacing-sm); } .mt-3 { margin-top: var(--spacing-md); } .mt-4 { margin-top: var(--spacing-lg); } .mt-5 { margin-top: var(--spacing-xl); } .mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: var(--spacing-xs); } .mb-2 { margin-bottom: var(--spacing-sm); } .mb-3 { margin-bottom: var(--spacing-md); } .mb-4 { margin-bottom: var(--spacing-lg); } .mb-5 { margin-bottom: var(--spacing-xl); } .p-0 { padding: 0; } .p-1 { padding: var(--spacing-xs); } .p-2 { padding: var(--spacing-sm); } .p-3 { padding: var(--spacing-md); } .p-4 { padding: var(--spacing-lg); } .p-5 { padding: var(--spacing-xl); } .d-none { display: none !important; } .d-block { display: block !important; } .d-flex { display: flex !important; } .d-grid { display: grid !important; } .flex-column { flex-direction: column; } .flex-wrap { flex-wrap: wrap; } .justify-center { justify-content: center; } .justify-between { justify-content: space-between; } .align-center { align-items: center; } .align-start { align-items: flex-start; } .align-end { align-items: flex-end; } .gap-1 { gap: var(--spacing-xs); } .gap-2 { gap: var(--spacing-sm); } .gap-3 { gap: var(--spacing-md); } .gap-4 { gap: var(--spacing-lg); } .gap-5 { gap: var(--spacing-xl); } .w-100 { width: 100%; } .h-100 { height: 100%; } .rounded { border-radius: var(--border-radius); } .rounded-lg { border-radius: var(--border-radius-lg); } .rounded-xl { border-radius: var(--border-radius-xl); } .shadow-sm { box-shadow: var(--shadow-sm); } .shadow-md { box-shadow: var(--shadow-md); } .shadow-lg { box-shadow: var(--shadow-lg); } .shadow-xl { box-shadow: var(--shadow-xl); } .bg-primary { background: var(--primary-gradient); color: white; } .bg-secondary { background: var(--bg-secondary); } .bg-dark { background: var(--bg-dark); color: white; } .text-primary { color: var(--text-primary); } .text-secondary { color: var(--text-secondary); } .text-light { color: var(--text-light); } .text-success { color: var(--secondary-color); } .text-danger { color: var(--danger-color); } .text-warning { color: var(--warning-color); } .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } /* Адаптивность */ @media (max-width: 1200px) { :root { --spacing-xxl: 48px; --spacing-xl: 40px; --spacing-lg: 28px; --font-size-4xl: 56px; --font-size-3xl: 40px; --font-size-2xl: 28px; --font-size-xl: 22px; } } @media (max-width: 992px) { :root { --spacing-xxl: 40px; --spacing-xl: 32px; --spacing-lg: 24px; --font-size-4xl: 48px; --font-size-3xl: 36px; --font-size-2xl: 24px; --font-size-xl: 20px; } .section { padding: var(--spacing-xl) 0; } } @media (max-width: 768px) { :root { --spacing-xxl: 32px; --spacing-xl: 24px; --spacing-lg: 20px; --spacing-md: 16px; --font-size-4xl: 40px; --font-size-3xl: 32px; --font-size-2xl: 22px; --font-size-xl: 18px; --font-size-lg: 16px; } .container { padding: 0 var(--spacing-md); } .btn-lg { padding: var(--spacing-md) var(--spacing-lg); font-size: var(--font-size-md); } } @media (max-width: 480px) { :root { --spacing-xxl: 24px; --font-size-4xl: 32px; --font-size-3xl: 28px; } .section { padding: var(--spacing-lg) 0; } } /* Печать */ @media print { .no-print { display: none !important; } body { font-size: 12pt; line-height: 1.4; } .container { max-width: 100%; padding: 0; } a { text-decoration: underline; } } /* Плавная загрузка */ .fade-in { animation: fadeIn 0.5s ease; } .slide-up { animation: slideUp 0.6s ease; } .slide-down { animation: slideDown 0.6s ease; } /* Скелетоны для загрузки */ .skeleton { background: linear-gradient(90deg, var(--border-color) 25%, color-mix(in srgb, var(--border-color) 50%, transparent) 50%, var(--border-color) 75% ); background-size: 200% 100%; animation: loading 1.5s infinite; } @keyframes loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* Прокрутка */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: var(--bg-secondary); } ::-webkit-scrollbar-thumb { background: var(--primary-color); border-radius: 5px; } ::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--primary-color) 80%, black); } /* Выделение текста */ ::selection { background: color-mix(in srgb, var(--primary-color) 30%, transparent); color: var(--text-primary); } ::-moz-selection { background: color-mix(in srgb, var(--primary-color) 30%, transparent); color: var(--text-primary); } /* Фокус для доступности */ :focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; } /* Поддержка prefers-reduced-motion */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } html { scroll-behavior: auto; } } /* Поддержка prefers-contrast */ @media (prefers-contrast: high) { :root { --primary-color: #0000ff; --secondary-color: #008000; --danger-color: #ff0000; --text-primary: #000000; --bg-primary: #ffffff; } }
// package.json { "name": "guarantor-landing", "version": "1.0.0", "description": "Лендинг для крипто-эскроу сервиса Guarantor.Su", "main": "app.js", "scripts": { "dev": "parcel src/index.html", "build": "parcel build src/index.html", "test": "jest", "lint": "eslint src/", "format": "prettier --write src/", "analyze": "webpack-bundle-analyzer dist/stats.json", "deploy": "npm run build && firebase deploy", "serve": "serve dist/", "cypress:open": "cypress open", "cypress:run": "cypress run", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook", "performance": "lighthouse http://localhost:3000 --output json --output-path ./lighthouse-report.json", "security": "npm audit", "generate-sitemap": "node scripts/generate-sitemap.js", "generate-robots": "node scripts/generate-robots.js", "optimize-images": "node scripts/optimize-images.js", "critical-css": "node scripts/critical-css.js" }, "dependencies": { "gsap": "^3.12.0", "swiper": "^9.0.0", "chart.js": "^4.0.0", "lottie-web": "^5.10.0", "axios": "^1.3.0", "vue": "^3.2.0", "vue-router": "^4.1.0", "pinia": "^2.0.0", "web3": "^1.8.0", "ethers": "^5.7.0", "crypto-js": "^4.1.0", "date-fns": "^2.29.0", "lodash": "^4.17.0", "validator": "^13.7.0", "uuid": "^9.0.0", "js-cookie": "^3.0.0", "clipboard": "^2.0.0", "keen-slider": "^6.0.0", "aos": "^2.3.0", "vanilla-tilt": "^1.7.0", "simplebar": "^6.0.0", "tippy.js": "^6.0.0", "flatpickr": "^4.6.0", "choices.js": "^9.0.0", "intl-tel-input": "^17.0.0", "inputmask": "^5.0.0", "autosize": "^5.0.0", "photoswipe": "^5.3.0" }, "devDependencies": { "parcel": "^2.8.0", "@parcel/transformer-sass": "^2.8.0", "@parcel/transformer-image": "^2.8.0", "@parcel/transformer-svg": "^2.8.0", "@parcel/packager-raw-url": "^2.8.0", "sass": "^1.56.0", "postcss": "^8.4.0", "autoprefixer": "^10.4.0", "cssnano": "^5.1.0", "typescript": "^4.9.0", "@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0", "eslint": "^8.0.0", "eslint-plugin-vue": "^9.0.0", "prettier": "^2.8.0", "stylelint": "^14.0.0", "stylelint-config-standard": "^29.0.0", "jest": "^29.0.0", "@testing-library/jest-dom": "^5.16.0", "@testing-library/user-event": "^14.0.0", "@testing-library/vue": "^6.0.0", "cypress": "^12.0.0", "@cypress/vite-dev-server": "^4.0.0", "webpack-bundle-analyzer": "^4.7.0", "lighthouse": "^9.0.0", "serve": "^14.0.0", "@storybook/vue3": "^7.0.0", "@storybook/addon-essentials": "^7.0.0", "@storybook/addon-interactions": "^7.0.0", "@storybook/addon-links": "^7.0.0", "@storybook/testing-library": "^0.0.0", "sharp": "^0.31.0", "svgo": "^3.0.0", "critical": "^4.0.0", "sitemap": "^7.0.0" }, "browserslist": [ "> 0.5%", "last 2 versions", "not dead", "not IE 11" ], "engines": { "node": ">=16.0.0", "npm": ">=8.0.0" }, "repository": { "type": "git", "url": "https://github.com/guarantor-su/landing.git" }, "keywords": [ "crypto", "escrow", "blockchain", "security", "fintech" ], "author": "Guarantor.Su Team", "license": "MIT", "bugs": { "url": "https://github.com/guarantor-su/landing/issues" }, "homepage": "https://guarantor.su" }
// webpack.config.js const path = require('path'); const webpack = require('webpack'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); const WorkboxPlugin = require('workbox-webpack-plugin'); module.exports = (env, argv) => { const isProduction = argv.mode === 'production'; return { mode: isProduction ? 'production' : 'development', entry: { main: './src/js/app.js', vendor: './src/js/vendor.js', styles: './src/css/main.scss' }, output: { path: path.resolve(__dirname, 'dist'), filename: isProduction ? 'js/[name].[contenthash].js' : 'js/[name].js', chunkFilename: isProduction ? 'js/[name].[contenthash].chunk.js' : 'js/[name].chunk.js', publicPath: '/', clean: true }, devtool: isProduction ? 'source-map' : 'eval-source-map', devServer: { static: { directory: path.join(__dirname, 'dist'), }, compress: true, port: 3000, hot: true, historyApiFallback: true, client: { overlay: { errors: true, warnings: false, }, progress: true, }, }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: ['@babel/plugin-transform-runtime'] } } }, { test: /\.scss$/, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader' ] }, { test: /\.css$/, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'postcss-loader' ] }, { test: /\.(png|jpg|jpeg|gif|webp|avif)$/i, type: 'asset', parser: { dataUrlCondition: { maxSize: 4 * 1024 // 4kb } }, generator: { filename: 'images/[name].[contenthash][ext][query]' } }, { test: /\.svg$/, use: [ { loader: '@svgr/webpack', options: { svgoConfig: { plugins: [ { name: 'removeViewBox', active: false }, { name: 'removeDimensions', active: true } ] } } }, 'url-loader' ] }, { test: /\.(woff|woff2|eot|ttf|otf)$/i, type: 'asset/resource', generator: { filename: 'fonts/[name].[contenthash][ext][query]' } }, { test: /\.html$/i, loader: 'html-loader', options: { sources: { list: [ { tag: 'img', attribute: 'src', type: 'src' }, { tag: 'source', attribute: 'srcset', type: 'srcset' } ] } } } ] }, optimization: { minimize: isProduction, minimizer: [ new TerserPlugin({ terserOptions: { compress: { drop_console: isProduction, drop_debugger: isProduction }, format: { comments: false } }, extractComments: false }), new CssMinimizerPlugin() ], splitChunks: { chunks: 'all', minSize: 20000, maxSize: 244000, minChunks: 1, maxAsyncRequests: 30, maxInitialRequests: 30, cacheGroups: { defaultVendors: { test: /[\\/]node_modules[\\/]/, priority: -10, reuseExistingChunk: true }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true }, styles: { name: 'styles', test: /\.css$/, chunks: 'all', enforce: true } } }, runtimeChunk: 'single' }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', inject: 'body', minify: isProduction ? { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true } : false, meta: { viewport: 'width=device-width, initial-scale=1.0', description: 'Guarantor.Su - безопасный эскроу-сервис для криптовалютных сделок', 'theme-color': '#6a51fe' } }), new MiniCssExtractPlugin({ filename: isProduction ? 'css/[name].[contenthash].css' : 'css/[name].css', chunkFilename: isProduction ? 'css/[name].[contenthash].chunk.css' : 'css/[name].chunk.css' }), new CopyWebpackPlugin({ patterns: [ { from: 'public', to: '', globOptions: { ignore: ['**/index.html'] } }, { from: 'src/robots.txt', to: 'robots.txt' }, { from: 'src/sitemap.xml', to: 'sitemap.xml' }, { from: 'src/.htaccess', to: '.htaccess' } ] }), new WebpackManifestPlugin({ fileName: 'asset-manifest.json', publicPath: '/', generate: (seed, files) => { const manifestFiles = files.reduce((manifest, file) => { manifest[file.name] = file.path; return manifest; }, seed); const entrypointFiles = files .filter(x => x.isInitial) .map(x => x.path); return { files: manifestFiles, entrypoints: entrypointFiles }; } }), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'), 'process.env.API_URL': JSON.stringify(isProduction ? 'https://api.guarantor.su' : 'http://localhost:3001'), 'process.env.GA_ID': JSON.stringify(process.env.GA_ID || 'UA-XXXXX-Y') }), ...(isProduction ? [ new CompressionPlugin({ algorithm: 'gzip', test: /\.(js|css|html|svg|json)$/, threshold: 10240, minRatio: 0.8 }), new CompressionPlugin({ algorithm: 'brotliCompress', test: /\.(js|css|html|svg|json)$/, compressionOptions: { level: 11 }, threshold: 10240, minRatio: 0.8 }), new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true, maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB navigateFallback: '/index.html', navigateFallbackAllowlist: [/^(?!\/__).*/], runtimeCaching: [ { urlPattern: /^https:\/\/api\.guarantor\.su\/api\//, handler: 'NetworkFirst', options: { cacheName: 'api-cache', expiration: { maxEntries: 50, maxAgeSeconds: 5 * 60 // 5 минут }, networkTimeoutSeconds: 10 } }, { urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/, handler: 'CacheFirst', options: { cacheName: 'image-cache', expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 // 30 дней } } }, { urlPattern: /\.(?:js|css)$/, handler: 'StaleWhileRevalidate', options: { cacheName: 'static-resources', expiration: { maxEntries: 100, maxAgeSeconds: 7 * 24 * 60 * 60 // 7 дней } } }, { urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com/, handler: 'CacheFirst', options: { cacheName: 'google-fonts', expiration: { maxEntries: 10, maxAgeSeconds: 365 * 24 * 60 * 60 // 1 год } } } ] }) ] : []), ...(process.env.ANALYZE ? [ new BundleAnalyzerPlugin({ analyzerMode: 'static', reportFilename: 'bundle-analysis.html', openAnalyzer: false }) ] : []) ], resolve: { extensions: ['.js', '.json', '.scss', '.css'], alias: { '@': path.resolve(__dirname, 'src'), '@components': path.resolve(__dirname, 'src/components'), '@styles': path.resolve(__dirname, 'src/styles'), '@images': path.resolve(__dirname, 'src/images'), '@utils': path.resolve(__dirname, 'src/utils') } }, performance: { hints: isProduction ? 'warning' : false, maxAssetSize: 244000, // 244kb maxEntrypointSize: 488000 // 488kb } }; };
// postcss.config.js module.exports = { plugins: [ require('autoprefixer'), require('cssnano')({ preset: [ 'default', { discardComments: { removeAll: true }, normalizeWhitespace: false } ] }) ] };
// .eslintrc.js module.exports = { root: true, env: { browser: true, es2021: true, node: true }, extends: [ 'eslint:recommended', 'plugin:vue/vue3-recommended', '@vue/typescript/recommended' ], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'vue/multi-word-component-names': 'off', 'vue/no-v-html': 'off', 'vue/require-default-prop': 'off', 'vue/require-prop-types': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', 'no-unused-vars': 'warn', 'prefer-const': 'warn', 'eqeqeq': ['error', 'always'], 'curly': ['error', 'all'], 'quotes': ['error', 'single', { avoidEscape: true }], 'semi': ['error', 'always'], 'indent': ['error', 2, { SwitchCase: 1 }], 'comma-dangle': ['error', 'never'], 'object-curly-spacing': ['error', 'always'], 'array-bracket-spacing': ['error', 'never'], 'space-in-parens': ['error', 'never'], 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], 'eol-last': ['error', 'always'] }, overrides: [ { files: [ '**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)' ], env: { jest: true } } ] };
// .prettierrc.js module.exports = { semi: true, trailingComma: 'none', singleQuote: true, printWidth: 100, tabWidth: 2, useTabs: false, bracketSpacing: true, arrowParens: 'avoid', endOfLine: 'lf', htmlWhitespaceSensitivity: 'css', proseWrap: 'preserve', quoteProps: 'as-needed', jsxSingleQuote: false, bracketSameLine: false, vueIndentScriptAndStyle: false, embeddedLanguageFormatting: 'auto', singleAttributePerLine: false };
{ "semi": true, "trailingComma": "none", "singleQuote": true, "printWidth": 100, "tabWidth": 2, "useTabs": false, "bracketSpacing": true, "arrowParens": "avoid", "endOfLine": "lf", "htmlWhitespaceSensitivity": "css", "proseWrap": "preserve", "quoteProps": "as-needed", "jsxSingleQuote": false, "bracketSameLine": false, "vueIndentScriptAndStyle": false, "embeddedLanguageFormatting": "auto", "singleAttributePerLine": false }
// cypress.config.js const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:3000', specPattern: 'tests/e2e/**/*.cy.{js,jsx,ts,tsx}', supportFile: 'tests/e2e/support/e2e.js', fixturesFolder: 'tests/e2e/fixtures', downloadsFolder: 'tests/e2e/downloads', videosFolder: 'tests/e2e/videos', screenshotsFolder: 'tests/e2e/screenshots', viewportWidth: 1920, viewportHeight: 1080, video: true, videoCompression: 32, screenshotOnRunFailure: true, trashAssetsBeforeRuns: true, defaultCommandTimeout: 10000, requestTimeout: 10000, responseTimeout: 30000, pageLoadTimeout: 60000, experimentalMemoryManagement: true, numTestsKeptInMemory: 5, retries: { runMode: 2, openMode: 0 }, env: { apiUrl: 'https://api.guarantor.su', googleTag: 'UA-XXXXX-Y' }, setupNodeEvents(on, config) { require('@cypress/code-coverage/task')(on, config); on('task', { log(message) { console.log(message); return null; }, table(message) { console.table(message); return null; } }); on('before:browser:launch', (browser = {}, launchOptions) => { if (browser.family === 'chromium' && browser.name !== 'electron') { launchOptions.args.push('--disable-dev-shm-usage'); launchOptions.args.push('--no-sandbox'); launchOptions.args.push('--disable-gpu'); launchOptions.args.push('--disable-web-security'); launchOptions.args.push('--disable-features=CrossSiteDocumentBlockingIfIsolating'); } if (browser.name === 'electron') { launchOptions.preferences.width = 1920; launchOptions.preferences.height = 1080; } return launchOptions; }); return config; } }, component: { devServer: { framework: 'vue', bundler: 'webpack' }, specPattern: 'tests/component/**/*.cy.{js,jsx,ts,tsx}' } });
// cypress.config.js const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:3000', specPattern: 'tests/e2e/**/*.cy.{js,jsx,ts,tsx}', supportFile: 'tests/e2e/support/e2e.js', fixturesFolder: 'tests/e2e/fixtures', downloadsFolder: 'tests/e2e/downloads', videosFolder: 'tests/e2e/videos', screenshotsFolder: 'tests/e2e/screenshots', viewportWidth: 1920, viewportHeight: 1080, video: true, videoCompression: 32, screenshotOnRunFailure: true, trashAssetsBeforeRuns: true, defaultCommandTimeout: 10000, requestTimeout: 10000, responseTimeout: 30000, pageLoadTimeout: 60000, experimentalMemoryManagement: true, numTestsKeptInMemory: 5, retries: { runMode: 2, openMode: 0 }, env: { apiUrl: 'https://api.guarantor.su', googleTag: 'UA-XXXXX-Y' }, setupNodeEvents(on, config) { require('@cypress/code-coverage/task')(on, config); on('task', { log(message) { console.log(message); return null; }, table(message) { console.table(message); return null; } }); on('before:browser:launch', (browser = {}, launchOptions) => { if (browser.family === 'chromium' && browser.name !== 'electron') { launchOptions.args.push('--disable-dev-shm-usage'); launchOptions.args.push('--no-sandbox'); launchOptions.args.push('--disable-gpu'); launchOptions.args.push('--disable-web-security'); launchOptions.args.push('--disable-features=CrossSiteDocumentBlockingIfIsolating'); } if (browser.name === 'electron') { launchOptions.preferences.width = 1920; launchOptions.preferences.height = 1080; } return launchOptions; }); return config; } }, component: { devServer: { framework: 'vue', bundler: 'webpack' }, specPattern: 'tests/component/**/*.cy.{js,jsx,ts,tsx}' } });
// scripts/generate-sitemap.js const { SitemapStream, streamToPromise } = require('sitemap'); const { createWriteStream } = require('fs'); const { resolve } = require('path'); const pages = [ { url: '/', changefreq: 'daily', priority: 1.0 }, { url: '/how-it-works', changefreq: 'weekly', priority: 0.9 }, { url: '/deal-types', changefreq: 'weekly', priority: 0.9 }, { url: '/security', changefreq: 'monthly', priority: 0.8 }, { url: '/pricing', changefreq: 'monthly', priority: 0.8 }, { url: '/faq', changefreq: 'weekly', priority: 0.7 }, { url: '/blog', changefreq: 'daily', priority: 0.6 }, { url: '/about', changefreq: 'monthly', priority: 0.5 }, { url: '/contacts', changefreq: 'monthly', priority: 0.5 }, { url: '/privacy', changefreq: 'yearly', priority: 0.3 }, { url: '/terms', changefreq: 'yearly', priority: 0.3 } ]; async function generateSitemap() { const sitemap = new SitemapStream({ hostname: 'https://guarantor.su', lastmodDateOnly: false, xmlns: { news: false, xhtml: false, image: false, video: false } }); const writeStream = createWriteStream(resolve(__dirname, '../dist/sitemap.xml')); sitemap.pipe(writeStream); pages.forEach(page => { sitemap.write({ url: page.url, changefreq: page.changefreq, priority: page.priority, lastmod: new Date().toISOString() }); }); sitemap.end(); await streamToPromise(sitemap); console.log('Sitemap generated successfully!'); } generateSitemap().catch(console.error);
// scripts/critical-css.js const critical = require('critical'); const path = require('path'); critical.generate({ base: path.resolve(__dirname, '../dist/'), src: 'index.html', target: { css: 'css/critical.css', uncritical: 'css/non-critical.css' }, width: 1300, height: 900, inline: false, extract: true, ignore: { atrule: ['@font-face'], rule: [/url\(/], decl: (node, value) => /url\(/.test(value) }, penthouse: { timeout: 60000, maxEmbeddedBase64Length: 1000, forceInclude: [ '.hero', '.hero-title', '.hero-subtitle', '.hero-cta', '.btn-primary' ] } }).then(() => { console.log('Critical CSS generated!'); }).catch(err => { console.error('Critical CSS generation failed:', err); });