Browser extension for SecureBit Chat — a P2P messenger with military-grade cryptography.

This commit is contained in:
SecureBitChat
2025-10-10 00:06:58 -04:00
commit 6eedc0fa55
69 changed files with 31889 additions and 0 deletions

3636
src/app.jsx Normal file

File diff suppressed because it is too large Load Diff

492
src/components/QRScanner.js Normal file
View File

@@ -0,0 +1,492 @@
// Simple QR Scanner Component using only Html5Qrcode
const QRScanner = ({ onScan, onClose, isVisible, continuous = false }) => {
const videoRef = React.useRef(null);
const qrScannerRef = React.useRef(null);
const [error, setError] = React.useState(null);
const [isScanning, setIsScanning] = React.useState(false);
const [progress, setProgress] = React.useState({ id: null, seq: 0, total: 0 });
const [showFocusHint, setShowFocusHint] = React.useState(false);
const [manualMode, setManualMode] = React.useState(false);
const [scannedParts, setScannedParts] = React.useState(new Set());
const [currentQRId, setCurrentQRId] = React.useState(null);
React.useEffect(() => {
if (isVisible) {
startScanner();
} else {
stopScanner();
}
return () => {
stopScanner();
};
}, [isVisible]);
React.useEffect(() => {
const onProgress = (e) => {
const { id, seq, total } = e.detail || {};
if (!id || !total) return;
setProgress({ id, seq, total });
// Обновляем ID текущего QR кода
if (id !== currentQRId) {
setCurrentQRId(id);
setScannedParts(new Set()); // Сбрасываем сканированные части для нового ID
}
// Добавляем отсканированную часть
setScannedParts(prev => new Set([...prev, seq]));
};
const onComplete = () => {
// Close scanner once app signals completion
if (!continuous) return;
try { stopScanner(); } catch {}
};
document.addEventListener('qr-scan-progress', onProgress, { passive: true });
document.addEventListener('qr-scan-complete', onComplete, { passive: true });
return () => {
document.removeEventListener('qr-scan-progress', onProgress, { passive: true });
document.removeEventListener('qr-scan-complete', onComplete, { passive: true });
};
}, [currentQRId]);
// Функция для tap-to-focus
const handleTapToFocus = (event, html5Qrcode) => {
try {
// Показываем подсказку о фокусировке
setShowFocusHint(true);
setTimeout(() => setShowFocusHint(false), 2000);
// Получаем координаты клика относительно видео элемента
const rect = event.target.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// Нормализуем координаты (0-1)
const normalizedX = x / rect.width;
const normalizedY = y / rect.height;
console.log('Tap to focus at:', { x, y, normalizedX, normalizedY });
// Попытка программной фокусировки (если поддерживается браузером)
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
// Это может не работать во всех браузерах, но попробуем
console.log('Attempting programmatic focus...');
}
} catch (error) {
console.warn('Tap to focus error:', error);
}
};
// Функции для ручного управления
const toggleManualMode = () => {
setManualMode(!manualMode);
if (!manualMode) {
// При включении ручного режима останавливаем автопрокрутку
console.log('Manual mode enabled - auto-scroll disabled');
} else {
// При выключении ручного режима возобновляем автопрокрутку
console.log('Manual mode disabled - auto-scroll enabled');
}
};
const resetProgress = () => {
setScannedParts(new Set());
setCurrentQRId(null);
setProgress({ id: null, seq: 0, total: 0 });
};
const startScanner = async () => {
try {
console.log('Starting QR scanner...');
setError(null);
setIsScanning(true);
// Allow camera on HTTP as well; rely on browser permission prompts
// Check if Html5Qrcode is available
if (!window.Html5Qrcode) {
setError('QR scanner library not loaded');
setIsScanning(false);
return;
}
// Get available cameras first
console.log('Getting available cameras...');
const cameras = await window.Html5Qrcode.getCameras();
console.log('Available cameras:', cameras);
if (!cameras || cameras.length === 0) {
setError('No cameras found on this device');
setIsScanning(false);
return;
}
// Clear any existing scanner
if (qrScannerRef.current) {
try {
qrScannerRef.current.stop();
} catch (e) {
console.log('Stopping previous scanner:', e.message);
}
}
// Create video element if it doesn't exist
if (!videoRef.current) {
console.log('Video element not found');
setError('Video element not found');
setIsScanning(false);
return;
}
console.log('Video element found:', videoRef.current);
console.log('Video element ID:', videoRef.current.id);
// Create Html5Qrcode instance
console.log('Creating Html5Qrcode instance...');
const html5Qrcode = new window.Html5Qrcode(videoRef.current.id || 'qr-reader');
// Find back camera (environment facing)
let cameraId = cameras[0].id; // Default to first camera
let selectedCamera = cameras[0];
// Look for back camera
for (const camera of cameras) {
if (camera.label.toLowerCase().includes('back') ||
camera.label.toLowerCase().includes('rear') ||
camera.label.toLowerCase().includes('environment')) {
cameraId = camera.id;
selectedCamera = camera;
break;
}
}
console.log('Available cameras:');
cameras.forEach((cam, index) => {
console.log(`${index + 1}. ${cam.label} (${cam.id})`);
});
console.log('Selected camera:', selectedCamera.label, 'ID:', cameraId);
// Start camera
console.log('Starting camera with Html5Qrcode...');
const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024);
const qrboxSize = isDesktop ? 560 : 360;
await html5Qrcode.start(
cameraId, // Use specific camera ID
{
fps: /iPhone|iPad|iPod/i.test(navigator.userAgent) ? 2 : 3,
qrbox: { width: qrboxSize, height: qrboxSize },
// Улучшенные настройки для мобильных устройств
aspectRatio: 1.0,
videoConstraints: {
focusMode: "continuous", // Непрерывная автофокусировка
exposureMode: "continuous", // Непрерывная экспозиция
whiteBalanceMode: "continuous", // Непрерывный баланс белого
torch: false, // Вспышка выключена по умолчанию
facingMode: "environment" // Используем заднюю камеру
}
},
(decodedText, decodedResult) => {
console.log('QR Code detected:', decodedText);
try {
const res = onScan(decodedText);
const handleResult = (val) => {
const shouldClose = val === true || !continuous;
if (shouldClose) {
stopScanner();
}
};
if (res && typeof res.then === 'function') {
res.then(handleResult).catch((e) => {
console.warn('onScan async handler error:', e);
if (!continuous) stopScanner();
});
} else {
handleResult(res);
}
} catch (e) {
console.warn('onScan handler threw:', e);
if (!continuous) {
stopScanner();
}
}
},
(error) => {
// Ignore decode errors, they're normal during scanning
console.log('QR decode error:', error);
}
);
// Store scanner reference
qrScannerRef.current = html5Qrcode;
console.log('QR scanner started successfully');
// Добавляем обработчик tap-to-focus для мобильных устройств
if (videoRef.current) {
videoRef.current.addEventListener('click', (event) => {
handleTapToFocus(event, html5Qrcode);
});
}
} catch (err) {
console.error('Error starting QR scanner:', err);
let errorMessage = 'Failed to start camera';
if (err.name === 'NotAllowedError') {
errorMessage = 'Camera access denied. Please allow camera access and try again.';
} else if (err.name === 'NotFoundError') {
errorMessage = 'No camera found on this device.';
} else if (err.name === 'NotSupportedError') {
errorMessage = 'Camera not supported on this device.';
} else if (err.name === 'NotReadableError') {
errorMessage = 'Camera is already in use by another application.';
} else if (err.message) {
errorMessage = err.message;
}
setError(errorMessage);
setIsScanning(false);
}
};
const stopScanner = () => {
if (qrScannerRef.current) {
try {
qrScannerRef.current.stop().then(() => {
console.log('QR scanner stopped');
}).catch((err) => {
console.log('Error stopping scanner:', err);
});
} catch (err) {
console.log('Error stopping scanner:', err);
}
qrScannerRef.current = null;
}
setIsScanning(false);
try {
// iOS Safari workaround: small delay before closing modal to release camera
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
setTimeout(() => {
// no-op; allow camera to settle
}, 150);
}
} catch {}
};
const handleClose = () => {
stopScanner();
onClose();
};
if (!isVisible) {
return null;
}
return React.createElement('div', {
className: "fixed inset-0 bg-black/80 flex items-center justify-center z-50"
}, [
React.createElement('div', {
key: 'scanner-modal',
className: "bg-gray-800 rounded-lg p-6 w-full mx-4 max-w-2xl"
}, [
React.createElement('div', {
key: 'scanner-header',
className: "flex items-center justify-between mb-4"
}, [
React.createElement('h3', {
key: 'title',
className: "text-lg font-medium text-white"
}, 'Scan QR Code'),
React.createElement('button', {
key: 'close-btn',
onClick: handleClose,
className: "text-gray-400 hover:text-white transition-colors"
}, [
React.createElement('i', {
className: 'fas fa-times text-xl'
})
])
]),
// Индикатор прогресса сканирования
progress.total > 1 && React.createElement('div', {
key: 'progress-indicator',
className: "mb-4 p-3 bg-gray-800/50 border border-gray-600/30 rounded-lg"
}, [
React.createElement('div', {
key: 'progress-header',
className: "flex items-center justify-between mb-2"
}, [
React.createElement('span', {
key: 'progress-title',
className: "text-sm text-gray-300"
}, `QR ID: ${currentQRId ? currentQRId.substring(0, 8) + '...' : 'N/A'}`),
React.createElement('span', {
key: 'progress-count',
className: "text-sm text-blue-400"
}, `${scannedParts.size}/${progress.total} scanned`)
]),
React.createElement('div', {
key: 'progress-numbers',
className: "flex flex-wrap gap-1"
}, Array.from({ length: progress.total }, (_, i) => {
const partNumber = i + 1;
const isScanned = scannedParts.has(partNumber);
return React.createElement('div', {
key: `part-${partNumber}`,
className: `w-8 h-8 rounded-full flex items-center justify-center text-xs font-medium transition-colors ${
isScanned
? 'bg-green-500 text-white'
: 'bg-gray-600 text-gray-300'
}`
}, partNumber);
}))
]),
// Панель управления
progress.total > 1 && React.createElement('div', {
key: 'control-panel',
className: "mb-4 flex gap-2"
}, [
React.createElement('button', {
key: 'manual-toggle',
onClick: toggleManualMode,
className: `px-3 py-1 rounded text-xs font-medium transition-colors ${
manualMode
? 'bg-blue-500 text-white'
: 'bg-gray-600 text-gray-300 hover:bg-gray-500'
}`
}, manualMode ? 'Manual Mode' : 'Auto Mode'),
React.createElement('button', {
key: 'reset-progress',
onClick: resetProgress,
className: "px-3 py-1 bg-red-500/20 text-red-400 border border-red-500/20 rounded text-xs font-medium hover:bg-red-500/30"
}, 'Reset'),
React.createElement('span', {
key: 'mode-hint',
className: "text-xs text-gray-400 self-center"
}, manualMode ? 'Tap to focus, scan manually' : 'Auto-scrolling enabled')
]),
React.createElement('div', {
key: 'scanner-content',
className: "relative"
}, [
React.createElement('div', {
key: 'video-container',
id: 'qr-reader',
ref: videoRef,
className: "w-full h-80 md:h-[32rem] bg-gray-700 rounded-lg"
}),
error && React.createElement('div', {
key: 'error',
className: "absolute inset-0 flex items-center justify-center bg-red-900/50 rounded-lg"
}, [
React.createElement('div', {
key: 'error-content',
className: "text-center text-white p-4"
}, [
React.createElement('i', {
key: 'error-icon',
className: 'fas fa-exclamation-triangle text-2xl mb-2'
}),
React.createElement('p', {
key: 'error-text',
className: "text-sm"
}, error)
])
]),
!error && !isScanning && React.createElement('div', {
key: 'loading',
className: "absolute inset-0 flex items-center justify-center bg-gray-700/50 rounded-lg"
}, [
React.createElement('div', {
key: 'loading-content',
className: "text-center text-white"
}, [
React.createElement('i', {
key: 'loading-icon',
className: 'fas fa-spinner fa-spin text-2xl mb-2'
}),
React.createElement('p', {
key: 'loading-text',
className: "text-sm"
}, 'Starting camera...')
])
]),
!error && isScanning && React.createElement('div', {
key: 'scanning-overlay',
className: "absolute inset-0 flex items-center justify-center"
}, [
React.createElement('div', {
key: 'scanning-content',
className: "text-center text-white bg-black/50 rounded-lg px-4 py-2"
}, [
React.createElement('i', {
key: 'scanning-icon',
className: 'fas fa-qrcode text-xl mb-1'
}),
React.createElement('p', {
key: 'scanning-text',
className: "text-xs"
}, progress && progress.total > 1 ? `Frames: ${Math.min(progress.seq, progress.total)}/${progress.total}` : 'Point camera at QR code'),
React.createElement('p', {
key: 'tap-hint',
className: "text-xs text-blue-300 mt-1"
}, 'Tap screen to focus')
])
]),
// Подсказка о фокусировке
showFocusHint && React.createElement('div', {
key: 'focus-hint',
className: "absolute top-4 left-1/2 transform -translate-x-1/2 bg-green-500/90 text-white px-3 py-1 rounded-full text-xs font-medium z-10"
}, 'Focusing...'),
// Bottom overlay kept simple on mobile
]),
// Дополнительные подсказки для улучшения сканирования
React.createElement('div', {
key: 'scanning-tips',
className: "mt-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg"
}, [
React.createElement('h4', {
key: 'tips-title',
className: "text-blue-400 text-sm font-medium mb-2 flex items-center"
}, [
React.createElement('i', {
key: 'tips-icon',
className: 'fas fa-lightbulb mr-2'
}),
'Tips for better scanning:'
]),
React.createElement('ul', {
key: 'tips-list',
className: "text-xs text-blue-300 space-y-1"
}, [
React.createElement('li', {
key: 'tip-1'
}, '• Ensure good lighting'),
React.createElement('li', {
key: 'tip-2'
}, '• Hold phone steady'),
React.createElement('li', {
key: 'tip-3'
}, '• Tap screen to focus'),
React.createElement('li', {
key: 'tip-4'
}, '• Keep QR code in frame')
])
])
])
]);
};
// Export for use in other files
window.QRScanner = QRScanner;
console.log('QRScanner component loaded and available on window.QRScanner');

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,320 @@
const ComparisonTable = () => {
const [selectedFeature, setSelectedFeature] = React.useState(null);
const messengers = [
{
name: "SecureBit.chat",
logo: <div className="w-8 h-8 bg-orange-500/10 border border-orange-500/20 rounded-lg flex items-center justify-center">
<i className="fas fa-shield-halved text-orange-400" />
</div>,
type: "P2P WebRTC",
version: "Latest",
color: "orange",
},
{
name: "Signal",
logo: (
<svg className="w-8 h-8" viewBox="0 0 122.88 122.31" xmlns="http://www.w3.org/2000/svg">
<path className="fill-blue-500" d="M27.75,0H95.13a27.83,27.83,0,0,1,27.75,27.75V94.57a27.83,27.83,0,0,1-27.75,27.74H27.75A27.83,27.83,0,0,1,0,94.57V27.75A27.83,27.83,0,0,1,27.75,0Z" />
<path className="fill-white" d="M61.44,25.39A35.76,35.76,0,0,0,31.18,80.18L27.74,94.86l14.67-3.44a35.77,35.77,0,1,0,19-66Z" />
</svg>
),
type: "Centralized",
version: "Latest",
color: "blue",
},
{
name: "Threema",
logo: (
<svg className="w-8 h-8" viewBox="0 0 122.88 122.88" xmlns="http://www.w3.org/2000/svg">
<rect width="122.88" height="122.88" rx="18.43" fill="#474747" />
<path fill="#FFFFFF" d="M44.26,78.48l-19.44,4.8l4.08-16.56c-4.08-5.28-6.48-12-6.48-18.96c0-18.96,17.52-34.32,39.12-34.32c21.6,0,39.12,15.36,39.12,34.32c0,18.96-17.52,34.32-39.12,34.32c-6,0-12-1.2-17.04-3.36L44.26,78.48z M50.26,44.64h-0.48c-0.96,0-1.68,0.72-1.44,1.68v15.6c0,0.96,0.72,1.68,1.68,1.68l23.04,0c0.96,0,1.68-0.72,1.68-1.68v-15.6c0-0.96-0.72-1.68-1.68-1.68h-0.48v-4.32c0-6-5.04-11.04-11.04-11.04S50.5,34.32,50.5,40.32v4.32H50.26z M68.02,44.64h-13.2v-4.32c0-3.6,2.88-6.72,6.72-6.72c3.6,0,6.72,2.88,6.72,6.72v4.32H68.02z" />
<circle cx="37.44" cy="97.44" r="6.72" fill="#3fe669" />
<circle cx="61.44" cy="97.44" r="6.72" fill="#3fe669" />
<circle cx="85.44" cy="97.44" r="6.72" fill="#3fe669" />
</svg>
),
type: "Centralized",
version: "Latest",
color: "green",
},
{
name: "Session",
logo: (
<svg className="w-8 h-8" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<rect width="1024" height="1024" fill="#333132" />
<path fill="#00f782" d="M431 574.8c-.8-7.4-6.7-8.2-10.8-10.6-13.6-7.9-27.5-15.4-41.3-23l-22.5-12.3c-8.5-4.7-17.1-9.2-25.6-14.1-10.5-6-21-11.9-31.1-18.6-18.9-12.5-33.8-29.1-46.3-48.1-8.3-12.6-14.8-26.1-19.2-40.4-6.7-21.7-10.8-44.1-7.8-66.8 1.8-14 4.6-28 9.7-41.6 7.8-20.8 19.3-38.8 34.2-54.8 9.8-10.6 21.2-19.1 33.4-26.8 14.7-9.3 30.7-15.4 47.4-19 13.8-3 28.1-4.3 42.2-4.4 89.9-.4 179.7-.3 269.6 0 12.6 0 25.5 1 37.7 4.1 24.3 6.2 45.7 18.2 63 37 11.2 12.2 20.4 25.8 25.8 41.2 7.3 20.7 12.3 42.1 6.7 64.4-2.1 8.5-2.7 17.5-6.1 25.4-4.7 10.9-10.8 21.2-17.2 31.2-8.7 13.5-20.5 24.3-34.4 32.2-10.1 5.7-21 10.2-32 14.3-18.1 6.7-37.2 5-56.1 5.2-17.2.2-34.5 0-51.7.1-1.7 0-3.4 1.2-5.1 1.9 1.3 1.8 2.1 4.3 3.9 5.3 13.5 7.8 27.2 15.4 40.8 22.9 11 6 22.3 11.7 33.2 17.9 15.2 8.5 30.2 17.4 45.3 26.1 19.3 11.1 34.8 26.4 47.8 44.3 9.7 13.3 17.2 27.9 23 43.5 6.1 16.6 9.2 33.8 10.4 51.3.6 9.1-.7 18.5-1.9 27.6-1.2 9.1-2.7 18.4-5.6 27.1-3.3 10.2-7.4 20.2-12.4 29.6-8.4 15.7-19.6 29.4-32.8 41.4-12.7 11.5-26.8 20.6-42.4 27.6-22.9 10.3-46.9 14.4-71.6 14.5-89.7.3-179.4.2-269.1-.1-12.6 0-25.5-1-37.7-3.9-24.5-5.7-45.8-18-63.3-36.4-11.6-12.3-20.2-26.5-26.6-41.9-2.7-6.4-4.1-13.5-5.4-20.4-1.5-8.1-2.8-16.3-3.1-24.5-.6-15.7 2.8-30.9 8.2-45.4 8.2-22 21.7-40.6 40.2-55.2 10-7.9 21.3-13.7 33.1-18.8 16.6-7.2 34-8.1 51.4-8.5 21.9-.5 43.9-.1 65.9-.1 1.9-.1 3.9-.3 6.2-.4zm96.3-342.4c0 .1 0 .1 0 0-48.3.1-96.6-.6-144.9.5-13.5.3-27.4 3.9-40.1 8.7-14.9 5.6-28.1 14.6-39.9 25.8-20.2 19-32.2 42.2-37.2 68.9-3.6 19-1.4 38.1 4.1 56.5 4.1 13.7 10.5 26.4 18.5 38.4 14.8 22.2 35.7 36.7 58.4 49.2 11 6.1 22.2 11.9 33.2 18 13.5 7.5 26.9 15.1 40.4 22.6 13.1 7.3 26.2 14.5 39.2 21.7 9.7 5.3 19.4 10.7 29.1 16.1 2.9 1.6 4.1.2 4.5-2.4.3-2 .3-4 .3-6.1v-58.8c0-19.9.1-39.9 0-59.8 0-6.6 1.7-12.8 7.6-16.1 3.5-2 8.2-2.8 12.4-2.8 50.3-.2 100.7-.2 151-.1 19.8 0 38.3-4.4 55.1-15.1 23.1-14.8 36.3-36.3 40.6-62.9 3.4-20.8-1-40.9-12.4-58.5-17.8-27.5-43.6-43-76.5-43.6-47.8-.8-95.6-.2-143.4-.2zm-30.6 559.7c45.1 0 90.2-.2 135.3.1 18.9.1 36.6-3.9 53.9-11.1 18.4-7.7 33.6-19.8 46.3-34.9 9.1-10.8 16.2-22.9 20.8-36.5 4.2-12.4 7.4-24.7 7.3-37.9-.1-10.3.2-20.5-3.4-30.5-2.6-7.2-3.4-15.2-6.4-22.1-3.9-8.9-8.9-17.3-14-25.5-12.9-20.8-31.9-34.7-52.8-46.4-10.6-5.9-21.2-11.6-31.8-17.5-10.3-5.7-20.4-11.7-30.7-17.4-11.2-6.1-22.5-11.9-33.7-18-16.6-9.1-33.1-18.4-49.8-27.5-4.9-2.7-6.1-1.9-6.4 3.9-.1 2-.1 4.1-.1 6.1v114.5c0 14.8-5.6 20.4-20.4 20.4-47.6.1-95.3-.1-142.9.2-10.5.1-21.1 1.4-31.6 2.8-16.5 2.2-30.5 9.9-42.8 21-17 15.5-27 34.7-29.4 57.5-1.1 10.9-.4 21.7 2.9 32.5 3.7 12.3 9.2 23.4 17.5 33 19.2 22.1 43.4 33.3 72.7 33.3 46.6.1 93 0 139.5 0z" />
</svg>
),
type: "Onion Network",
version: "Latest",
color: "cyan",
},
];
const features = [
{
name: "Security Architecture",
lockbit: { status: "trophy", detail: "18-layer military-grade defense system with complete ASN.1 validation" },
signal: { status: "check", detail: "Signal Protocol with double ratchet" },
threema: { status: "check", detail: "Standard security implementation" },
session: { status: "check", detail: "Modified Signal Protocol + Onion routing" },
},
{
name: "Cryptography",
lockbit: { status: "trophy", detail: "ECDH P-384 + AES-GCM 256 + ECDSA P-384" },
signal: { status: "check", detail: "Signal Protocol + Double Ratchet" },
threema: { status: "check", detail: "NaCl + XSalsa20 + Poly1305" },
session: { status: "check", detail: "Modified Signal Protocol" },
},
{
name: "Perfect Forward Secrecy",
lockbit: { status: "trophy", detail: "Auto rotation every 5 minutes or 100 messages" },
signal: { status: "check", detail: "Double Ratchet algorithm" },
threema: { status: "warning", detail: "Partial (group chats)" },
session: { status: "check", detail: "Session Ratchet algorithm" },
},
{
name: "Architecture",
lockbit: { status: "trophy", detail: "Pure P2P WebRTC without servers" },
signal: { status: "times", detail: "Centralized Signal servers" },
threema: { status: "times", detail: "Threema servers in Switzerland" },
session: { status: "warning", detail: "Onion routing via network nodes" },
},
{
name: "Registration Anonymity",
lockbit: { status: "trophy", detail: "No registration required, instant anonymous channels" },
signal: { status: "times", detail: "Phone number required" },
threema: { status: "check", detail: "ID generated locally" },
session: { status: "check", detail: "Random session ID" },
},
{
name: "Payment Integration",
lockbit: { status: "trophy", detail: "Lightning Network satoshis per session + WebLN" },
signal: { status: "times", detail: "No payment system" },
threema: { status: "times", detail: "No payment system" },
session: { status: "times", detail: "No payment system" },
},
{
name: "Metadata Protection",
lockbit: { status: "trophy", detail: "Full metadata encryption + traffic obfuscation" },
signal: { status: "warning", detail: "Sealed Sender (partial)" },
threema: { status: "warning", detail: "Minimal metadata" },
session: { status: "check", detail: "Onion routing hides metadata" },
},
{
name: "Traffic Obfuscation",
lockbit: { status: "trophy", detail: "Fake traffic + pattern masking + packet padding" },
signal: { status: "times", detail: "No traffic obfuscation" },
threema: { status: "times", detail: "No traffic obfuscation" },
session: { status: "check", detail: "Onion routing provides obfuscation" },
},
{
name: "Open Source",
lockbit: { status: "trophy", detail: "100% open + auditable + MIT license" },
signal: { status: "check", detail: "Fully open" },
threema: { status: "warning", detail: "Only clients open" },
session: { status: "check", detail: "Fully open" },
},
{
name: "MITM Protection",
lockbit: { status: "trophy", detail: "Out-of-band verification + mutual auth + ECDSA" },
signal: { status: "check", detail: "Safety numbers verification" },
threema: { status: "check", detail: "QR code scanning" },
session: { status: "warning", detail: "Basic key verification" },
},
{
name: "Economic Model",
lockbit: { status: "trophy", detail: "Sustainable pay-per-session model" },
signal: { status: "warning", detail: "Donations and grants dependency" },
threema: { status: "check", detail: "One-time app purchase" },
session: { status: "warning", detail: "Donations dependency" },
},
{
name: "Censorship Resistance",
lockbit: { status: "trophy", detail: "Impossible to block P2P + no servers to target" },
signal: { status: "warning", detail: "Blocked in authoritarian countries" },
threema: { status: "warning", detail: "May be blocked" },
session: { status: "check", detail: "Onion routing bypasses blocks" },
},
{
name: "Data Storage",
lockbit: { status: "trophy", detail: "Zero data storage - only in browser memory" },
signal: { status: "warning", detail: "Local database storage" },
threema: { status: "warning", detail: "Local + optional backup" },
session: { status: "warning", detail: "Local database storage" },
},
{
name: "Key Security",
lockbit: { status: "trophy", detail: "Non-extractable keys + hardware protection" },
signal: { status: "check", detail: "Secure key storage" },
threema: { status: "check", detail: "Local key storage" },
session: { status: "check", detail: "Secure key storage" },
},
{
name: "Post-Quantum Roadmap",
lockbit: { status: "check", detail: "Planned v5.0 - CRYSTALS-Kyber/Dilithium" },
signal: { status: "warning", detail: "PQXDH in development" },
threema: { status: "times", detail: "Not announced" },
session: { status: "times", detail: "Not announced" },
},
];
const getStatusIcon = (status) => {
const statusMap = {
"trophy": { icon: "fa-trophy", color: "accent-orange" },
"check": { icon: "fa-check", color: "text-green-300" },
"warning": { icon: "fa-exclamation-triangle", color: "text-yellow-300" },
"times": { icon: "fa-times", color: "text-red-300" },
};
return statusMap[status] || { icon: "fa-question", color: "text-gray-400" };
};
const toggleFeatureDetail = (index) => {
setSelectedFeature(selectedFeature === index ? null : index);
};
return (
<div className="mt-16">
{/* Title */}
<div className="text-center mb-8">
<h3 className="text-3xl font-bold text-white mb-3">
Enhanced Security Edition Comparison
</h3>
<p className="text-gray-400 max-w-2xl mx-auto mb-4">
Enhanced Security Edition vs leading secure messengers
</p>
</div>
{/* Table container */}
<div className="max-w-7xl mx-auto">
{/* Mobile Alert */}
<div className="md:hidden p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mb-4">
<p className="text-yellow-400 text-sm text-center">
<i className="fas fa-lightbulb mr-2"></i>
Rotate your device horizontally for better viewing
</p>
</div>
{/* Table */}
<div className="overflow-x-auto">
<table
className="w-full border-collapse rounded-xl overflow-hidden shadow-2xl"
style={{ backgroundColor: "rgba(42, 43, 42, 0.9)" }}
>
{/* Table Header */}
<thead>
<tr className="bg-black-table">
<th className="text-left p-4 border-b border-gray-600 text-white font-bold min-w-[240px]">
Security Criterion
</th>
{messengers.map((messenger, index) => (
<th key={`messenger-${index}`} className="text-center p-4 border-b border-gray-600 min-w-[160px]">
<div className="flex flex-col items-center">
<div className="mb-2">{messenger.logo}</div>
<div className={`text-sm font-bold ${
messenger.color === 'orange' ? 'text-orange-400' :
messenger.color === 'blue' ? 'text-blue-400' :
messenger.color === 'green' ? 'text-green-400' :
'text-cyan-400'
}`}>
{messenger.name}
</div>
<div className="text-xs text-gray-400">{messenger.type}</div>
<div className="text-xs text-gray-500 mt-1">{messenger.version}</div>
</div>
</th>
))}
</tr>
</thead>
{/* Table body */}
<tbody>
{features.map((feature, featureIndex) => (
<React.Fragment key={`feature-${featureIndex}`}>
<tr
className={`border-b border-gray-700/30 transition-all duration-200 cursor-pointer hover:bg-[rgb(20_20_20_/30%)] ${
selectedFeature === featureIndex ? 'bg-[rgb(20_20_20_/50%)]' : ''
}`}
onClick={() => toggleFeatureDetail(featureIndex)}
>
<td className="p-4 text-white font-semibold">
<div className="flex items-center justify-between">
<span>{feature.name}</span>
<i className={`fas fa-chevron-${selectedFeature === featureIndex ? 'up' : 'down'} text-xs text-gray-400 opacity-60 transition-all duration-200`} />
</div>
</td>
<td className="p-4 text-center">
<i className={`fas ${getStatusIcon(feature.lockbit.status).icon} ${getStatusIcon(feature.lockbit.status).color} text-2xl`} />
</td>
<td className="p-4 text-center">
<i className={`fas ${getStatusIcon(feature.signal.status).icon} ${getStatusIcon(feature.signal.status).color} text-2xl`} />
</td>
<td className="p-4 text-center">
<i className={`fas ${getStatusIcon(feature.threema.status).icon} ${getStatusIcon(feature.threema.status).color} text-2xl`} />
</td>
<td className="p-4 text-center">
<i className={`fas ${getStatusIcon(feature.session.status).icon} ${getStatusIcon(feature.session.status).color} text-2xl`} />
</td>
</tr>
{/* Details */}
{selectedFeature === featureIndex && (
<tr className="border-b border-gray-700/30 bg-gradient-to-r from-gray-800/20 to-gray-900/20">
<td className="p-4 text-xs text-gray-400 font-medium">Technical Details:</td>
<td className="p-4 text-center">
<div className="text-xs text-orange-300 font-medium leading-relaxed">
{feature.lockbit.detail}
</div>
</td>
<td className="p-4 text-center">
<div className="text-xs text-blue-300 leading-relaxed">
{feature.signal.detail}
</div>
</td>
<td className="p-4 text-center">
<div className="text-xs text-green-300 leading-relaxed">
{feature.threema.detail}
</div>
</td>
<td className="p-4 text-center">
<div className="text-xs text-cyan-300 leading-relaxed">
{feature.session.detail}
</div>
</td>
</tr>
)}
</React.Fragment>
))}
</tbody>
</table>
</div>
{/* Legend */}
<div className="mt-8 grid grid-cols-2 md:grid-cols-4 gap-4 max-w-5xl mx-auto">
<div className="flex items-center justify-center p-4 bg-orange-500/10 rounded-xl hover:bg-orange-500/40 transition-colors">
<i className="fas fa-trophy text-orange-400 mr-2 text-xl"></i>
<span className="text-orange-300 text-sm font-bold">Category Leader</span>
</div>
<div className="flex items-center justify-center p-4 bg-green-500/10 rounded-xl hover:bg-green-600/40 transition-colors">
<i className="fas fa-check text-green-300 mr-2 text-xl"></i>
<span className="text-green-200 text-sm font-bold">Excellent</span>
</div>
<div className="flex items-center justify-center p-4 bg-yellow-500/10 rounded-xl hover:bg-yellow-600/40 transition-colors">
<i className="fas fa-exclamation-triangle text-yellow-300 mr-2 text-xl"></i>
<span className="text-yellow-200 text-sm font-bold">Partial/Limited</span>
</div>
<div className="flex items-center justify-center p-4 bg-red-500/10 rounded-xl hover:bg-red-600/40 transition-colors">
<i className="fas fa-times text-red-300 mr-2 text-xl"></i>
<span className="text-red-200 text-sm font-bold">Not Available</span>
</div>
</div>
</div>
</div>
);
};
window.ComparisonTable = ComparisonTable;

View File

@@ -0,0 +1,86 @@
const DownloadApps = () => {
const apps = [
{ id: 'web', name: 'Web App', subtitle: 'Browser Version', icon: 'fas fa-globe', platform: 'Web', isActive: true, url: 'https://securebit.chat/', color: 'green' },
{ id: 'windows', name: 'Windows', subtitle: 'Desktop App', icon: 'fab fa-windows', platform: 'Desktop', isActive: false, url: 'https://securebit.chat/download/windows/SecureBit%20Chat%20Setup%204.1.222.exe', color: 'blue' },
{ id: 'macos', name: 'macOS', subtitle: 'Desktop App', icon: 'fab fa-safari', platform: 'Desktop', isActive: false, url: '#', color: 'gray' },
{ id: 'linux', name: 'Linux', subtitle: 'Desktop App', icon: 'fab fa-linux', platform: 'Desktop', isActive: false, url: '#', color: 'orange' },
{ id: 'ios', name: 'iOS', subtitle: 'iPhone & iPad', icon: 'fab fa-apple', platform: 'Mobile', isActive: false, url: 'https://apps.apple.com/app/securebit-chat/', color: 'white' },
{ id: 'android', name: 'Android', subtitle: 'Google Play', icon: 'fab fa-android', platform: 'Mobile', isActive: false, url: 'https://play.google.com/store/apps/details?id=com.securebit.chat', color: 'green' },
{ id: 'chrome', name: 'Chrome', subtitle: 'Browser Extension', icon: 'fab fa-chrome', platform: 'Browser', isActive: false, url: '#', color: 'yellow' },
{ id: 'edge', name: 'Edge', subtitle: 'Browser Extension', icon: 'fab fa-edge', platform: 'Browser', isActive: false, url: '#', color: 'blue' },
{ id: 'opera', name: 'Opera', subtitle: 'Browser Extension', icon: 'fab fa-opera', platform: 'Browser', isActive: false, url: '#', color: 'red' },
{ id: 'firefox', name: 'Firefox', subtitle: 'Browser Extension', icon: 'fab fa-firefox-browser', platform: 'Browser', isActive: false, url: '#', color: 'orange' },
];
const handleDownload = (app) => {
if (app.isActive) window.open(app.url, '_blank');
};
const desktopApps = apps.filter(a => a.platform === 'Desktop' || a.platform === 'Web');
const mobileApps = apps.filter(a => a.platform === 'Mobile');
const browserApps = apps.filter(a => a.platform === 'Browser');
const cardSize = "w-28 h-28";
const colorClasses = {
green: 'text-green-500',
blue: 'text-blue-500',
gray: 'text-gray-500',
orange: 'text-orange-500',
red: 'text-red-500',
white: 'text-white',
yellow: 'text-yellow-400',
};
const renderAppCard = (app) => (
React.createElement('div', {
key: app.id,
className: `group relative ${cardSize} rounded-2xl overflow-hidden card-minimal cursor-pointer`
}, [
React.createElement('i', {
key: 'bg-icon',
className: `${app.icon} absolute text-[3rem] ${app.isActive ? colorClasses[app.color] : 'text-white/10'} top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none transition-all duration-500 group-hover:scale-105`
}),
React.createElement('div', {
key: 'overlay',
className: "absolute inset-0 bg-black/30 backdrop-blur-md flex flex-col items-center justify-center text-center opacity-0 transition-opacity duration-300 group-hover:opacity-100"
}, [
React.createElement('h4', { key: 'name', className: `text-sm font-semibold text-primary mb-1` }, app.name),
React.createElement('p', { key: 'subtitle', className: `text-xs text-secondary mb-2` }, app.subtitle),
app.isActive ?
React.createElement('button', {
key: 'btn',
onClick: () => handleDownload(app),
className: `px-2 py-1 rounded-xl bg-emerald-500 text-black font-medium hover:bg-emerald-600 transition-colors text-xs`
}, app.id === "web" ? "Launch" : "Download")
:
React.createElement('span', { key: 'coming', className: "text-gray-400 font-medium text-xs" }, "Coming Soon")
])
])
);
return React.createElement('div', { className: "mt-20 px-6" }, [
// Header
React.createElement('div', { key: 'header', className: "text-center max-w-3xl mx-auto mb-12" }, [
React.createElement('h3', { key: 'title', className: "text-3xl font-bold text-primary mb-3" }, 'Download SecureBit.chat'),
React.createElement('p', { key: 'subtitle', className: "text-secondary text-lg mb-5" }, 'Stay secure on every device. Choose your platform and start chatting privately.')
]),
// Desktop Apps
React.createElement('div', { key: 'desktop-row', className: "hidden sm:flex justify-center flex-wrap gap-6 mb-6" },
desktopApps.map(renderAppCard)
),
// Mobile Apps
React.createElement('div', { key: 'mobile-row', className: "flex justify-center gap-6 mb-6" },
mobileApps.map(renderAppCard)
),
// Browser Extensions
React.createElement('div', { key: 'browser-row', className: "flex justify-center gap-6" },
browserApps.map(renderAppCard)
)
]);
};
window.DownloadApps = DownloadApps;

View File

@@ -0,0 +1,423 @@
// File Transfer Component for Chat Interface - Fixed Version
const FileTransferComponent = ({ webrtcManager, isConnected }) => {
const [dragOver, setDragOver] = React.useState(false);
const [transfers, setTransfers] = React.useState({ sending: [], receiving: [] });
const [readyFiles, setReadyFiles] = React.useState([]); // файлы, готовые к скачиванию
const fileInputRef = React.useRef(null);
// Update transfers periodically
React.useEffect(() => {
if (!isConnected || !webrtcManager) return;
const updateTransfers = () => {
const currentTransfers = webrtcManager.getFileTransfers();
setTransfers(currentTransfers);
};
const interval = setInterval(updateTransfers, 500);
return () => clearInterval(interval);
}, [isConnected, webrtcManager]);
// Setup file transfer callbacks - ИСПРАВЛЕНИЕ: НЕ отправляем промежуточные сообщения в чат
React.useEffect(() => {
if (!webrtcManager) return;
webrtcManager.setFileTransferCallbacks(
// Progress callback - ТОЛЬКО обновляем UI, НЕ отправляем в чат
(progress) => {
// Обновляем только локальное состояние
const currentTransfers = webrtcManager.getFileTransfers();
setTransfers(currentTransfers);
// НЕ отправляем сообщения в чат!
},
// File received callback - добавляем кнопку скачивания в UI
(fileData) => {
// Добавляем в список готовых к скачиванию
setReadyFiles(prev => {
// избегаем дублей по fileId
if (prev.some(f => f.fileId === fileData.fileId)) return prev;
return [...prev, {
fileId: fileData.fileId,
fileName: fileData.fileName,
fileSize: fileData.fileSize,
mimeType: fileData.mimeType,
getBlob: fileData.getBlob,
getObjectURL: fileData.getObjectURL,
revokeObjectURL: fileData.revokeObjectURL
}];
});
// Обновляем список активных передач
const currentTransfers = webrtcManager.getFileTransfers();
setTransfers(currentTransfers);
},
// Error callback
(error) => {
const currentTransfers = webrtcManager.getFileTransfers();
setTransfers(currentTransfers);
// ИСПРАВЛЕНИЕ: НЕ дублируем сообщения об ошибках
// Уведомления об ошибках уже отправляются в WebRTC менеджере
}
);
}, [webrtcManager]);
const handleFileSelect = async (files) => {
if (!isConnected || !webrtcManager) {
alert('Соединение не установлено. Сначала установите соединение.');
return;
}
// Дополнительная проверка состояния соединения
if (!webrtcManager.isConnected() || !webrtcManager.isVerified) {
alert('Соединение не готово для передачи файлов. Дождитесь завершения установки соединения.');
return;
}
for (const file of files) {
try {
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Валидация файла перед отправкой
const validation = webrtcManager.validateFile(file);
if (!validation.isValid) {
const errorMessage = validation.errors.join('. ');
alert(`Файл ${file.name} не может быть отправлен: ${errorMessage}`);
continue;
}
await webrtcManager.sendFile(file);
} catch (error) {
// Более мягкая обработка ошибок - не закрываем сессию
// Показываем пользователю ошибку, но не закрываем соединение
if (error.message.includes('Connection not ready')) {
alert(`Файл ${file.name} не может быть отправлен сейчас. Проверьте соединение и попробуйте снова.`);
} else if (error.message.includes('File too large') || error.message.includes('exceeds maximum')) {
alert(`Файл ${file.name} слишком большой: ${error.message}`);
} else if (error.message.includes('Maximum concurrent transfers')) {
alert(`Достигнут лимит одновременных передач. Дождитесь завершения текущих передач.`);
} else if (error.message.includes('File type not allowed')) {
alert(`Тип файла ${file.name} не поддерживается: ${error.message}`);
} else {
alert(`Ошибка отправки файла ${file.name}: ${error.message}`);
}
}
}
};
const handleDrop = (e) => {
e.preventDefault();
setDragOver(false);
const files = Array.from(e.dataTransfer.files);
handleFileSelect(files);
};
const handleDragOver = (e) => {
e.preventDefault();
setDragOver(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
setDragOver(false);
};
const handleFileInputChange = (e) => {
const files = Array.from(e.target.files);
handleFileSelect(files);
e.target.value = ''; // Reset input
};
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const getStatusIcon = (status) => {
switch (status) {
case 'metadata_sent':
case 'preparing':
return 'fas fa-cog fa-spin';
case 'transmitting':
case 'receiving':
return 'fas fa-exchange-alt fa-pulse';
case 'assembling':
return 'fas fa-puzzle-piece fa-pulse';
case 'completed':
return 'fas fa-check text-green-400';
case 'failed':
return 'fas fa-times text-red-400';
default:
return 'fas fa-circle';
}
};
const getStatusText = (status) => {
switch (status) {
case 'metadata_sent':
return 'Подготовка...';
case 'transmitting':
return 'Отправка...';
case 'receiving':
return 'Получение...';
case 'assembling':
return 'Сборка файла...';
case 'completed':
return 'Завершено';
case 'failed':
return 'Ошибка';
default:
return status;
}
};
if (!isConnected) {
return React.createElement('div', {
className: "p-4 text-center text-muted"
}, 'Передача файлов доступна только при установленном соединении');
}
// Проверяем дополнительное состояние соединения
const isConnectionReady = webrtcManager && webrtcManager.isConnected() && webrtcManager.isVerified;
if (!isConnectionReady) {
return React.createElement('div', {
className: "p-4 text-center text-yellow-600"
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-exclamation-triangle mr-2'
}),
'Соединение устанавливается... Передача файлов будет доступна после завершения установки.'
]);
}
return React.createElement('div', {
className: "file-transfer-component"
}, [
// File Drop Zone
React.createElement('div', {
key: 'drop-zone',
className: `file-drop-zone ${dragOver ? 'drag-over' : ''}`,
onDrop: handleDrop,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onClick: () => fileInputRef.current?.click()
}, [
React.createElement('div', {
key: 'drop-content',
className: "drop-content"
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-cloud-upload-alt text-2xl mb-2 text-blue-400'
}),
React.createElement('p', {
key: 'text',
className: "text-primary font-medium"
}, 'Drag files here or click to select'),
React.createElement('p', {
key: 'subtext',
className: "text-muted text-sm"
}, 'Maximum size: 100 MB per file')
])
]),
// Hidden file input
React.createElement('input', {
key: 'file-input',
ref: fileInputRef,
type: 'file',
multiple: true,
className: 'hidden',
onChange: handleFileInputChange
}),
// Active Transfers
(transfers.sending.length > 0 || transfers.receiving.length > 0) && React.createElement('div', {
key: 'transfers',
className: "active-transfers mt-4"
}, [
React.createElement('h4', {
key: 'title',
className: "text-primary font-medium mb-3 flex items-center"
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-exchange-alt mr-2'
}),
'Передача файлов'
]),
// Sending files
...transfers.sending.map(transfer =>
React.createElement('div', {
key: `send-${transfer.fileId}`,
className: "transfer-item bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 mb-2"
}, [
React.createElement('div', {
key: 'header',
className: "flex items-center justify-between mb-2"
}, [
React.createElement('div', {
key: 'info',
className: "flex items-center"
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-upload text-blue-400 mr-2'
}),
React.createElement('span', {
key: 'name',
className: "text-primary font-medium text-sm"
}, transfer.fileName),
React.createElement('span', {
key: 'size',
className: "text-muted text-xs ml-2"
}, formatFileSize(transfer.fileSize))
]),
React.createElement('button', {
key: 'cancel',
onClick: () => webrtcManager.cancelFileTransfer(transfer.fileId),
className: "text-red-400 hover:text-red-300 text-xs"
}, [
React.createElement('i', {
className: 'fas fa-times'
})
])
]),
React.createElement('div', {
key: 'progress',
className: "progress-bar"
}, [
React.createElement('div', {
key: 'fill',
className: "progress-fill bg-blue-400",
style: { width: `${transfer.progress}%` }
}),
React.createElement('div', {
key: 'text',
className: "progress-text text-xs flex items-center justify-between"
}, [
React.createElement('span', {
key: 'status',
className: "flex items-center"
}, [
React.createElement('i', {
key: 'icon',
className: `${getStatusIcon(transfer.status)} mr-1`
}),
getStatusText(transfer.status)
]),
React.createElement('span', {
key: 'percent'
}, `${transfer.progress.toFixed(1)}%`)
])
])
])
),
// Receiving files
...transfers.receiving.map(transfer =>
React.createElement('div', {
key: `recv-${transfer.fileId}`,
className: "transfer-item bg-green-500/10 border border-green-500/20 rounded-lg p-3 mb-2"
}, [
React.createElement('div', {
key: 'header',
className: "flex items-center justify-between mb-2"
}, [
React.createElement('div', {
key: 'info',
className: "flex items-center"
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-download text-green-400 mr-2'
}),
React.createElement('span', {
key: 'name',
className: "text-primary font-medium text-sm"
}, transfer.fileName),
React.createElement('span', {
key: 'size',
className: "text-muted text-xs ml-2"
}, formatFileSize(transfer.fileSize))
]),
React.createElement('div', { key: 'actions', className: 'flex items-center space-x-2' }, [
(() => {
const rf = readyFiles.find(f => f.fileId === transfer.fileId);
if (!rf || transfer.status !== 'completed') return null;
return React.createElement('button', {
key: 'download',
className: 'text-green-400 hover:text-green-300 text-xs flex items-center',
onClick: async () => {
try {
const url = await rf.getObjectURL();
const a = document.createElement('a');
a.href = url;
a.download = rf.fileName || 'file';
a.click();
rf.revokeObjectURL(url);
} catch (e) {
alert('Failed to start download: ' + e.message);
}
}
}, [
React.createElement('i', { key: 'i', className: 'fas fa-download mr-1' }),
'Download'
]);
})(),
React.createElement('button', {
key: 'cancel',
onClick: () => webrtcManager.cancelFileTransfer(transfer.fileId),
className: "text-red-400 hover:text-red-300 text-xs"
}, [
React.createElement('i', {
className: 'fas fa-times'
})
])
])
]),
React.createElement('div', {
key: 'progress',
className: "progress-bar"
}, [
React.createElement('div', {
key: 'fill',
className: "progress-fill bg-green-400",
style: { width: `${transfer.progress}%` }
}),
React.createElement('div', {
key: 'text',
className: "progress-text text-xs flex items-center justify-between"
}, [
React.createElement('span', {
key: 'status',
className: "flex items-center"
}, [
React.createElement('i', {
key: 'icon',
className: `${getStatusIcon(transfer.status)} mr-1`
}),
getStatusText(transfer.status)
]),
React.createElement('span', {
key: 'percent'
}, `${transfer.progress.toFixed(1)}%`)
])
])
])
)
])
]);
};
// Export
window.FileTransferComponent = FileTransferComponent;

View File

@@ -0,0 +1,708 @@
const EnhancedMinimalHeader = ({
status,
fingerprint,
verificationCode,
onDisconnect,
isConnected,
securityLevel,
sessionManager,
sessionTimeLeft,
webrtcManager
}) => {
const [currentTimeLeft, setCurrentTimeLeft] = React.useState(sessionTimeLeft || 0);
const [hasActiveSession, setHasActiveSession] = React.useState(false);
const [sessionType, setSessionType] = React.useState('unknown');
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
// ============================================
// FIXED SECURITY UPDATE LOGIC
// ============================================
React.useEffect(() => {
let isUpdating = false;
let lastUpdateAttempt = 0;
const updateRealSecurityStatus = async () => {
const now = Date.now();
if (now - lastUpdateAttempt < 10000) {
return;
}
if (isUpdating) {
return;
}
isUpdating = true;
lastUpdateAttempt = now;
try {
if (!webrtcManager || !isConnected) {
return;
}
const activeWebrtcManager = webrtcManager;
let realSecurityData = null;
if (typeof activeWebrtcManager.getRealSecurityLevel === 'function') {
realSecurityData = await activeWebrtcManager.getRealSecurityLevel();
} else if (typeof activeWebrtcManager.calculateAndReportSecurityLevel === 'function') {
realSecurityData = await activeWebrtcManager.calculateAndReportSecurityLevel();
} else {
realSecurityData = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(activeWebrtcManager);
}
if (realSecurityData && realSecurityData.isRealData !== false) {
const currentScore = realSecurityLevel?.score || 0;
const newScore = realSecurityData.score || 0;
if (currentScore !== newScore || !realSecurityLevel) {
setRealSecurityLevel(realSecurityData);
setLastSecurityUpdate(now);
} else if (window.DEBUG_MODE) {
}
} else {
console.warn(' Security calculation returned invalid data');
}
} catch (error) {
console.error(' Error in real security calculation:', error);
} finally {
isUpdating = false;
}
};
if (isConnected) {
updateRealSecurityStatus();
if (!realSecurityLevel || realSecurityLevel.score < 50) {
const retryInterval = setInterval(() => {
if (!realSecurityLevel || realSecurityLevel.score < 50) {
updateRealSecurityStatus();
} else {
clearInterval(retryInterval);
}
}, 5000);
setTimeout(() => clearInterval(retryInterval), 30000);
}
}
const interval = setInterval(updateRealSecurityStatus, 30000);
return () => clearInterval(interval);
}, [webrtcManager, isConnected]);
// ============================================
// FIXED EVENT HANDLERS
// ============================================
React.useEffect(() => {
const handleSecurityUpdate = (event) => {
setTimeout(() => {
setLastSecurityUpdate(0);
}, 100);
};
const handleRealSecurityCalculated = (event) => {
if (event.detail && event.detail.securityData) {
setRealSecurityLevel(event.detail.securityData);
setLastSecurityUpdate(Date.now());
}
};
document.addEventListener('security-level-updated', handleSecurityUpdate);
document.addEventListener('real-security-calculated', handleRealSecurityCalculated);
window.forceHeaderSecurityUpdate = (webrtcManager) => {
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager)
.then(securityData => {
if (securityData && securityData.isRealData !== false) {
setRealSecurityLevel(securityData);
setLastSecurityUpdate(Date.now());
console.log('✅ Header security level force-updated');
}
})
.catch(error => {
console.error('❌ Force update failed:', error);
});
} else {
setLastSecurityUpdate(0);
}
};
return () => {
document.removeEventListener('security-level-updated', handleSecurityUpdate);
document.removeEventListener('real-security-calculated', handleRealSecurityCalculated);
};
}, []);
// ============================================
// REST of the component logic
// ============================================
React.useEffect(() => {
// All security features are enabled by default - no session management needed
setHasActiveSession(true);
setCurrentTimeLeft(0);
setSessionType('premium'); // All features enabled
}, []);
React.useEffect(() => {
// All security features are enabled by default
setHasActiveSession(true);
setCurrentTimeLeft(0);
setSessionType('premium'); // All features enabled
}, [sessionTimeLeft]);
React.useEffect(() => {
const handleForceUpdate = (event) => {
// All security features are enabled by default
setHasActiveSession(true);
setCurrentTimeLeft(0);
setSessionType('premium'); // All features enabled
};
// Connection cleanup handler (use existing event from module)
const handleConnectionCleaned = () => {
if (window.DEBUG_MODE) {
console.log('🧹 Connection cleaned - clearing security data in header');
}
setRealSecurityLevel(null);
setLastSecurityUpdate(0);
setHasActiveSession(false);
setCurrentTimeLeft(0);
setSessionType('unknown');
};
const handlePeerDisconnect = () => {
if (window.DEBUG_MODE) {
console.log('👋 Peer disconnect detected - clearing security data in header');
}
setRealSecurityLevel(null);
setLastSecurityUpdate(0);
};
const handleDisconnected = () => {
setRealSecurityLevel(null);
setLastSecurityUpdate(0);
setHasActiveSession(false);
setCurrentTimeLeft(0);
setSessionType('unknown');
};
document.addEventListener('force-header-update', handleForceUpdate);
document.addEventListener('peer-disconnect', handlePeerDisconnect);
document.addEventListener('connection-cleaned', handleConnectionCleaned);
document.addEventListener('disconnected', handleDisconnected);
return () => {
document.removeEventListener('force-header-update', handleForceUpdate);
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
document.removeEventListener('disconnected', handleDisconnected);
};
}, []);
// ============================================
// SECURITY INDICATOR CLICK HANDLER
// ============================================
const handleSecurityClick = async (event) => {
// Check if it's a right-click or Ctrl+click to disconnect
if (event && (event.button === 2 || event.ctrlKey || event.metaKey)) {
if (onDisconnect && typeof onDisconnect === 'function') {
onDisconnect();
return;
}
}
// Prevent default behavior
event.preventDefault();
event.stopPropagation();
// Run real security tests if webrtcManager is available
let realTestResults = null;
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
try {
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
console.log('✅ Real security tests completed:', realTestResults);
} catch (error) {
console.error('❌ Real security tests failed:', error);
}
} else {
console.log('⚠️ Cannot run security tests:', {
webrtcManager: !!webrtcManager,
cryptoUtils: !!window.EnhancedSecureCryptoUtils
});
}
// If no real test results and no existing security level, show progress message
if (!realTestResults && !realSecurityLevel) {
alert('Security verification in progress...\nPlease wait for real-time cryptographic verification to complete.');
return;
}
// Use real test results if available, otherwise fall back to current data
let securityData = realTestResults || realSecurityLevel;
// If still no security data, create a basic fallback
if (!securityData) {
securityData = {
level: 'UNKNOWN',
score: 0,
color: 'gray',
verificationResults: {},
timestamp: Date.now(),
details: 'Security verification not available',
isRealData: false,
passedChecks: 0,
totalChecks: 0,
sessionType: 'unknown'
};
console.log('Using fallback security data:', securityData);
}
// Detailed information about the REAL security check
let message = `REAL-TIME SECURITY VERIFICATION\n\n`;
message += `Security Level: ${securityData.level} (${securityData.score}%)\n`;
message += `Session Type: ${securityData.sessionType || 'premium'}\n`;
message += `Verification Time: ${new Date(securityData.timestamp).toLocaleTimeString()}\n`;
message += `Data Source: ${securityData.isRealData ? 'Real Cryptographic Tests' : 'Simulated Data'}\n\n`;
if (securityData.verificationResults) {
message += 'DETAILED CRYPTOGRAPHIC TESTS:\n';
message += '=' + '='.repeat(40) + '\n';
const passedTests = Object.entries(securityData.verificationResults).filter(([key, result]) => result.passed);
const failedTests = Object.entries(securityData.verificationResults).filter(([key, result]) => !result.passed);
if (passedTests.length > 0) {
message += 'PASSED TESTS:\n';
passedTests.forEach(([key, result]) => {
const testName = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
message += ` ${testName}: ${result.details || 'Test passed'}\n`;
});
message += '\n';
}
if (failedTests.length > 0) {
message += 'FAILED/UNAVAILABLE TESTS:\n';
failedTests.forEach(([key, result]) => {
const testName = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
message += ` ${testName}: ${result.details || 'Test failed or unavailable'}\n`;
});
message += '\n';
}
message += `SUMMARY:\n`;
message += `Passed: ${securityData.passedChecks}/${securityData.totalChecks} tests\n`;
message += `Score: ${securityData.score}/${securityData.maxPossibleScore || 100} points\n\n`;
}
// Real security features status
message += `SECURITY FEATURES STATUS:\n`;
message += '=' + '='.repeat(40) + '\n';
if (securityData.verificationResults) {
const features = {
'ECDSA Digital Signatures': securityData.verificationResults.verifyECDSASignatures?.passed || false,
'ECDH Key Exchange': securityData.verificationResults.verifyECDHKeyExchange?.passed || false,
'AES-GCM Encryption': securityData.verificationResults.verifyEncryption?.passed || false,
'Message Integrity (HMAC)': securityData.verificationResults.verifyMessageIntegrity?.passed || false,
'Perfect Forward Secrecy': securityData.verificationResults.verifyPerfectForwardSecrecy?.passed || false,
'Replay Protection': securityData.verificationResults.verifyReplayProtection?.passed || false,
'DTLS Fingerprint': securityData.verificationResults.verifyDTLSFingerprint?.passed || false,
'SAS Verification': securityData.verificationResults.verifySASVerification?.passed || false,
'Metadata Protection': securityData.verificationResults.verifyMetadataProtection?.passed || false,
'Traffic Obfuscation': securityData.verificationResults.verifyTrafficObfuscation?.passed || false
};
Object.entries(features).forEach(([feature, isEnabled]) => {
message += `${isEnabled ? '✅' : '❌'} ${feature}\n`;
});
} else {
// Fallback if no verification results
message += `✅ ECDSA Digital Signatures\n`;
message += `✅ ECDH Key Exchange\n`;
message += `✅ AES-GCM Encryption\n`;
message += `✅ Message Integrity (HMAC)\n`;
message += `✅ Perfect Forward Secrecy\n`;
message += `✅ Replay Protection\n`;
message += `✅ DTLS Fingerprint\n`;
message += `✅ SAS Verification\n`;
message += `✅ Metadata Protection\n`;
message += `✅ Traffic Obfuscation\n`;
}
message += `\n${securityData.details || 'Real cryptographic verification completed'}`;
if (securityData.isRealData) {
message += '\n\n✅ This is REAL-TIME verification using actual cryptographic functions.';
} else {
message += '\n\n⚠ Warning: This data may be simulated. Connection may not be fully established.';
}
// Show in a more user-friendly way
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
font-family: monospace;
`;
const content = document.createElement('div');
content.style.cssText = `
background: #1a1a1a;
color: #fff;
padding: 20px;
border-radius: 8px;
max-width: 80%;
max-height: 80%;
overflow-y: auto;
white-space: pre-line;
border: 1px solid #333;
`;
content.textContent = message;
modal.appendChild(content);
// Close on click outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
// Close on Escape key
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleKeyDown);
}
};
document.addEventListener('keydown', handleKeyDown);
document.body.appendChild(modal);
};
// ============================================
// DISPLAY UTILITIES
// ============================================
const getStatusConfig = () => {
switch (status) {
case 'connected':
return {
text: 'Connected',
className: 'status-connected',
badgeClass: 'bg-green-500/10 text-green-400 border-green-500/20'
};
case 'verifying':
return {
text: 'Verifying...',
className: 'status-verifying',
badgeClass: 'bg-purple-500/10 text-purple-400 border-purple-500/20'
};
case 'connecting':
return {
text: 'Connecting...',
className: 'status-connecting',
badgeClass: 'bg-blue-500/10 text-blue-400 border-blue-500/20'
};
case 'retrying':
return {
text: 'Retrying...',
className: 'status-connecting',
badgeClass: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20'
};
case 'failed':
return {
text: 'Error',
className: 'status-failed',
badgeClass: 'bg-red-500/10 text-red-400 border-red-500/20'
};
case 'reconnecting':
return {
text: 'Reconnecting...',
className: 'status-connecting',
badgeClass: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20'
};
case 'peer_disconnected':
return {
text: 'Peer disconnected',
className: 'status-failed',
badgeClass: 'bg-orange-500/10 text-orange-400 border-orange-500/20'
};
default:
return {
text: 'Not connected',
className: 'status-disconnected',
badgeClass: 'bg-gray-500/10 text-gray-400 border-gray-500/20'
};
}
};
const config = getStatusConfig();
const displaySecurityLevel = isConnected ? (realSecurityLevel || securityLevel) : null;
const shouldShowTimer = hasActiveSession && currentTimeLeft > 0 && window.SessionTimer;
// ============================================
// DATA RELIABILITY INDICATOR
// ============================================
const getSecurityIndicatorDetails = () => {
if (!displaySecurityLevel) {
return {
tooltip: 'Security verification in progress...',
isVerified: false,
dataSource: 'loading'
};
}
const isRealData = displaySecurityLevel.isRealData !== false;
const baseTooltip = `${displaySecurityLevel.level} (${displaySecurityLevel.score}%)`;
if (isRealData) {
return {
tooltip: `${baseTooltip} - Real-time verification ✅\nRight-click or Ctrl+click to disconnect`,
isVerified: true,
dataSource: 'real'
};
} else {
return {
tooltip: `${baseTooltip} - Estimated (connection establishing...)\nRight-click or Ctrl+click to disconnect`,
isVerified: false,
dataSource: 'estimated'
};
}
};
const securityDetails = getSecurityIndicatorDetails();
// ============================================
// ADDING global methods for debugging
// ============================================
React.useEffect(() => {
window.debugHeaderSecurity = () => {
console.log('🔍 Header Security Debug:', {
realSecurityLevel,
lastSecurityUpdate,
isConnected,
webrtcManagerProp: !!webrtcManager,
windowWebrtcManager: !!window.webrtcManager,
cryptoUtils: !!window.EnhancedSecureCryptoUtils,
displaySecurityLevel: displaySecurityLevel,
securityDetails: securityDetails
});
};
return () => {
delete window.debugHeaderSecurity;
};
}, [realSecurityLevel, lastSecurityUpdate, isConnected, webrtcManager, displaySecurityLevel, securityDetails]);
// ============================================
// RENDER
// ============================================
return React.createElement('header', {
className: 'header-minimal sticky top-0 z-50'
}, [
React.createElement('div', {
key: 'container',
className: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8'
}, [
React.createElement('div', {
key: 'content',
className: 'flex items-center justify-between h-16'
}, [
// Logo and Title
React.createElement('div', {
key: 'logo-section',
className: 'flex items-center space-x-2 sm:space-x-3'
}, [
React.createElement('div', {
key: 'logo',
className: 'icon-container w-8 h-8 sm:w-10 sm:h-10'
}, [
React.createElement('i', {
className: 'fas fa-shield-halved accent-orange text-sm sm:text-base'
})
]),
React.createElement('div', {
key: 'title-section'
}, [
React.createElement('h1', {
key: 'title',
className: 'text-lg sm:text-xl font-semibold text-primary'
}, 'SecureBit.chat'),
React.createElement('p', {
key: 'subtitle',
className: 'text-xs sm:text-sm text-muted hidden sm:block'
}, 'End-to-end freedom v4.3.120')
])
]),
// Status and Controls - Responsive
React.createElement('div', {
key: 'status-section',
className: 'flex items-center space-x-2 sm:space-x-3'
}, [
// Session Timer - all features enabled by default
shouldShowTimer && React.createElement(window.SessionTimer, {
key: 'session-timer',
timeLeft: currentTimeLeft,
sessionType: sessionType,
onDisconnect: onDisconnect
}),
displaySecurityLevel && React.createElement('div', {
key: 'security-level',
className: 'hidden md:flex items-center space-x-2 cursor-pointer hover:opacity-80 transition-opacity duration-200',
onClick: handleSecurityClick,
onContextMenu: (e) => {
e.preventDefault();
if (onDisconnect && typeof onDisconnect === 'function') {
onDisconnect();
}
},
title: securityDetails.tooltip
}, [
React.createElement('div', {
key: 'security-icon',
className: `w-6 h-6 rounded-full flex items-center justify-center relative ${
displaySecurityLevel.color === 'green' ? 'bg-green-500/20' :
displaySecurityLevel.color === 'orange' ? 'bg-orange-500/20' :
displaySecurityLevel.color === 'yellow' ? 'bg-yellow-500/20' : 'bg-red-500/20'
} ${securityDetails.isVerified ? '' : 'animate-pulse'}`
}, [
React.createElement('i', {
className: `fas fa-shield-alt text-xs ${
displaySecurityLevel.color === 'green' ? 'text-green-400' :
displaySecurityLevel.color === 'orange' ? 'text-orange-400' :
displaySecurityLevel.color === 'yellow' ? 'text-yellow-400' : 'text-red-400'
}`
})
]),
React.createElement('div', {
key: 'security-info',
className: 'flex flex-col'
}, [
React.createElement('div', {
key: 'security-level-text',
className: 'text-xs font-medium text-primary flex items-center space-x-1'
}, [
React.createElement('span', {}, `${displaySecurityLevel.level} (${displaySecurityLevel.score}%)`)
]),
React.createElement('div', {
key: 'security-details',
className: 'text-xs text-muted mt-1 hidden lg:block'
}, securityDetails.dataSource === 'real' ?
`${displaySecurityLevel.passedChecks || 0}/${displaySecurityLevel.totalChecks || 0} tests` :
(displaySecurityLevel.details || `Stage ${displaySecurityLevel.stage || 1}`)
),
React.createElement('div', {
key: 'security-progress',
className: 'w-16 h-1 bg-gray-600 rounded-full overflow-hidden'
}, [
React.createElement('div', {
key: 'progress-bar',
className: `h-full transition-all duration-500 ${
displaySecurityLevel.color === 'green' ? 'bg-green-400' :
displaySecurityLevel.color === 'orange' ? 'bg-orange-400' :
displaySecurityLevel.color === 'yellow' ? 'bg-yellow-400' : 'bg-red-400'
}`,
style: { width: `${displaySecurityLevel.score}%` }
})
])
])
]),
// Mobile Security Indicator
displaySecurityLevel && React.createElement('div', {
key: 'mobile-security',
className: 'md:hidden flex items-center'
}, [
React.createElement('div', {
key: 'mobile-security-icon',
className: `w-8 h-8 rounded-full flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity duration-200 relative ${
displaySecurityLevel.color === 'green' ? 'bg-green-500/20' :
displaySecurityLevel.color === 'orange' ? 'bg-orange-500/20' :
displaySecurityLevel.color === 'yellow' ? 'bg-yellow-500/20' : 'bg-red-500/20'
} ${securityDetails.isVerified ? '' : 'animate-pulse'}`,
title: securityDetails.tooltip,
onClick: handleSecurityClick,
onContextMenu: (e) => {
e.preventDefault();
if (onDisconnect && typeof onDisconnect === 'function') {
onDisconnect();
}
}
}, [
React.createElement('i', {
className: `fas fa-shield-alt text-sm ${
displaySecurityLevel.color === 'green' ? 'text-green-400' :
displaySecurityLevel.color === 'orange' ? 'text-orange-400' :
displaySecurityLevel.color === 'yellow' ? 'text-yellow-400' : 'text-red-400'
}`
})
])
]),
// Status Badge
React.createElement('div', {
key: 'status-badge',
className: `px-2 sm:px-3 py-1.5 rounded-lg border ${config.badgeClass} flex items-center space-x-1 sm:space-x-2`
}, [
React.createElement('span', {
key: 'status-dot',
className: `status-dot ${config.className}`
}),
React.createElement('span', {
key: 'status-text',
className: 'text-xs sm:text-sm font-medium'
}, config.text),
]),
// Disconnect Button
isConnected && React.createElement('button', {
key: 'disconnect-btn',
onClick: onDisconnect,
className: 'p-1.5 sm:px-3 sm:py-1.5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 rounded-lg transition-all duration-200 text-sm'
}, [
React.createElement('i', {
className: 'fas fa-power-off sm:mr-2'
}),
React.createElement('span', {
className: 'hidden sm:inline'
}, 'Disconnect')
])
])
])
])
]);
};
window.EnhancedMinimalHeader = EnhancedMinimalHeader;

View File

@@ -0,0 +1,91 @@
const React = window.React;
const PasswordModal = ({ isOpen, onClose, onSubmit, action, password, setPassword }) => {
if (!isOpen) return null;
const handleSubmit = (e) => {
e.preventDefault();
if (password.trim()) {
onSubmit(password.trim());
setPassword('');
}
};
const getActionText = () => {
return action === 'offer' ? 'invitation' : 'response';
};
return React.createElement('div', {
className: 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4'
}, [
React.createElement('div', {
key: 'modal',
className: 'card-minimal rounded-xl p-6 max-w-md w-full border-purple-500/20'
}, [
React.createElement('div', {
key: 'header',
className: 'flex items-center mb-4'
}, [
React.createElement('div', {
key: 'icon',
className: 'w-10 h-10 bg-purple-500/10 border border-purple-500/20 rounded-lg flex items-center justify-center mr-3'
}, [
React.createElement('i', {
className: 'fas fa-key accent-purple'
})
]),
React.createElement('h3', {
key: 'title',
className: 'text-lg font-medium text-primary'
}, 'Password input')
]),
React.createElement('form', {
key: 'form',
onSubmit: handleSubmit,
className: 'space-y-4'
}, [
React.createElement('p', {
key: 'description',
className: 'text-secondary text-sm'
}, `Enter password for decryption ${getActionText()}:`),
React.createElement('input', {
key: 'password-input',
type: 'password',
value: password,
onChange: (e) => setPassword(e.target.value),
placeholder: 'Enter password...',
className: 'w-full p-3 bg-gray-900/30 border border-gray-500/20 rounded-lg text-primary placeholder-gray-500 focus:border-purple-500/40 focus:outline-none transition-all',
autoFocus: true
}),
React.createElement('div', {
key: 'buttons',
className: 'flex space-x-3'
}, [
React.createElement('button', {
key: 'submit',
type: 'submit',
className: 'flex-1 btn-primary text-white py-3 px-4 rounded-lg font-medium transition-all duration-200'
}, [
React.createElement('i', {
className: 'fas fa-unlock-alt mr-2'
}),
'Decrypt'
]),
React.createElement('button', {
key: 'cancel',
type: 'button',
onClick: onClose,
className: 'flex-1 btn-secondary text-white py-3 px-4 rounded-lg font-medium transition-all duration-200'
}, [
React.createElement('i', {
className: 'fas fa-times mr-2'
}),
'Cancel'
])
])
])
])
]);
};
window.PasswordModal = PasswordModal;

View File

@@ -0,0 +1,432 @@
function Roadmap() {
const [selectedPhase, setSelectedPhase] = React.useState(null);
const phases = [
{
version: "v1.0",
title: "Start of Development",
status: "done",
date: "Early 2025",
description: "Idea, prototype, and infrastructure setup",
features: [
"Concept and requirements formation",
"Stack selection: WebRTC, P2P, cryptography",
"First messaging prototypes",
"Repository creation and CI",
"Basic encryption architecture",
"UX/UI design"
]
},
{
version: "v1.5",
title: "Alpha Release",
status: "done",
date: "Spring 2025",
description: "First public alpha: basic chat and key exchange",
features: [
"Basic P2P messaging via WebRTC",
"Simple E2E encryption (demo scheme)",
"Stable signaling and reconnection",
"Minimal UX for testing",
"Feedback collection from early testers"
]
},
{
version: "v2.0",
title: "Security Hardened",
status: "done",
date: "Summer 2025",
description: "Security strengthening and stable branch release",
features: [
"ECDH/ECDSA implementation in production",
"Perfect Forward Secrecy and key rotation",
"Improved authentication checks",
"File encryption and large payload transfers",
"Audit of basic cryptoprocesses"
]
},
{
version: "v3.0",
title: "Scaling & Stability",
status: "done",
date: "Fall 2025",
description: "Network scaling and stability improvements",
features: [
"Optimization of P2P connections and NAT traversal",
"Reconnection mechanisms and message queues",
"Reduced battery consumption on mobile",
"Support for multi-device synchronization",
"Monitoring and logging tools for developers"
]
},
{
version: "v3.5",
title: "Privacy-first Release",
status: "done",
date: "Winter 2025",
description: "Focus on privacy: minimizing metadata",
features: [
"Metadata protection and fingerprint reduction",
"Experiments with onion routing and DHT",
"Options for anonymous connections",
"Preparation for open code audit",
"Improved user verification processes"
]
},
// current and future phases
{
version: "v4.3.120",
title: "Enhanced Security Edition",
status: "current",
date: "Now",
description: "Current version with ECDH + DTLS + SAS security, 18-layer military-grade cryptography and complete ASN.1 validation",
features: [
"ECDH + DTLS + SAS triple-layer security",
"ECDH P-384 + AES-GCM 256-bit encryption",
"DTLS fingerprint verification",
"SAS (Short Authentication String) verification",
"Perfect Forward Secrecy with key rotation",
"Enhanced MITM attack prevention",
"Complete ASN.1 DER validation",
"OID and EC point verification",
"SPKI structure validation",
"P2P WebRTC architecture",
"Metadata protection",
"100% open source code"
]
},
{
version: "v4.5",
title: "Mobile & Desktop Edition",
status: "development",
date: "Q2 2025",
description: "Native apps for all platforms",
features: [
"PWA app for mobile",
"Electron app for desktop",
"Real-time notifications",
"Automatic reconnection",
"Battery optimization",
"Cross-device synchronization",
"Improved UX/UI",
"Support for files up to 100MB"
]
},
{
version: "v5.0",
title: "Quantum-Resistant Edition",
status: "planned",
date: "Q4 2025",
description: "Protection against quantum computers",
features: [
"Post-quantum cryptography CRYSTALS-Kyber",
"SPHINCS+ digital signatures",
"Hybrid scheme: classic + PQ",
"Quantum-safe key exchange",
"Updated hashing algorithms",
"Migration of existing sessions",
"Compatibility with v4.x",
"Quantum-resistant protocols"
]
},
{
version: "v5.5",
title: "Group Communications",
status: "planned",
date: "Q2 2026",
description: "Group chats with preserved privacy",
features: [
"P2P group connections up to 8 participants",
"Mesh networking for groups",
"Signal Double Ratchet for groups",
"Anonymous groups without metadata",
"Ephemeral groups (disappear after session)",
"Cryptographic group administration",
"Group member auditing"
]
},
{
version: "v6.0",
title: "Decentralized Network",
status: "research",
date: "2027",
description: "Fully decentralized network",
features: [
"LockBit node mesh network",
"DHT for peer discovery",
"Built-in onion routing",
"Tokenomics and node incentives",
"Governance via DAO",
"Interoperability with other networks",
"Cross-platform compatibility",
"Self-healing network"
]
},
{
version: "v7.0",
title: "AI Privacy Assistant",
status: "research",
date: "2028+",
description: "AI for privacy and security",
features: [
"Local AI threat analysis",
"Automatic MITM detection",
"Adaptive cryptography",
"Personalized security recommendations",
"Zero-knowledge machine learning",
"Private AI assistant",
"Predictive security",
"Autonomous attack protection"
]
}
];
const getStatusConfig = (status) => {
switch (status) {
case 'current':
return {
color: 'green',
bgClass: 'bg-green-500/10 border-green-500/20',
textClass: 'text-green-400',
icon: 'fas fa-check-circle',
label: 'Current Version'
};
case 'development':
return {
color: 'orange',
bgClass: 'bg-orange-500/10 border-orange-500/20',
textClass: 'text-orange-400',
icon: 'fas fa-code',
label: 'In Development'
};
case 'planned':
return {
color: 'blue',
bgClass: 'bg-blue-500/10 border-blue-500/20',
textClass: 'text-blue-400',
icon: 'fas fa-calendar-alt',
label: 'Planned'
};
case 'research':
return {
color: 'purple',
bgClass: 'bg-purple-500/10 border-purple-500/20',
textClass: 'text-purple-400',
icon: 'fas fa-flask',
label: 'Research'
};
case 'done':
return {
color: 'gray',
bgClass: 'bg-gray-500/10 border-gray-500/20',
textClass: 'text-gray-300',
icon: 'fas fa-flag-checkered',
label: 'Released'
};
default:
return {
color: 'gray',
bgClass: 'bg-gray-500/10 border-gray-500/20',
textClass: 'text-gray-400',
icon: 'fas fa-question',
label: 'Unknown'
};
}
};
const togglePhaseDetail = (index) => {
setSelectedPhase(selectedPhase === index ? null : index);
};
return (
<div key="roadmap-section" className="mt-16 px-4 sm:px-0">
<div key="section-header" className="text-center mb-12">
<h3 key="title" className="text-2xl font-semibold text-primary mb-3">
Development Roadmap
</h3>
<p key="subtitle" className="text-secondary max-w-2xl mx-auto mb-6">
Evolution of SecureBit.chat : from initial development to quantum-resistant decentralized network with complete ASN.1 validation
</p>
</div>
<div key="roadmap-container" className="max-w-6xl mx-auto">
<div key="timeline" className="relative">
{/* The line has been removed */}
<div key="phases" className="space-y-8">
{phases.map((phase, index) => {
const statusConfig = getStatusConfig(phase.status);
const isExpanded = selectedPhase === index;
return (
<div key={`phase-${index}`} className="relative">
{/* The dots are visible only on sm and larger screens */}
<button
type="button"
aria-expanded={isExpanded}
onClick={() => togglePhaseDetail(index)}
key={`phase-button-${index}`}
className={`card-minimal rounded-xl p-4 text-left w-full transition-all duration-300 ${
isExpanded
? "ring-2 ring-" + statusConfig.color + "-500/30"
: ""
}`}
>
<div
key="phase-header"
className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 space-y-2 sm:space-y-0"
>
<div
key="phase-info"
className="flex flex-col sm:flex-row sm:items-center sm:space-x-4"
>
<div
key="version-badge"
className={`px-3 py-1 ${statusConfig.bgClass} border rounded-lg mb-2 sm:mb-0`}
>
<span
key="version"
className={`${statusConfig.textClass} font-bold text-sm`}
>
{phase.version}
</span>
</div>
<div key="title-section">
<h4
key="title"
className="text-lg font-semibold text-primary"
>
{phase.title}
</h4>
<p
key="description"
className="text-secondary text-sm"
>
{phase.description}
</p>
</div>
</div>
<div
key="phase-meta"
className="flex items-center space-x-3 text-sm text-gray-400 font-medium"
>
<div
key="status-badge"
className={`flex items-center px-3 py-1 ${statusConfig.bgClass} border rounded-lg`}
>
<i
key="status-icon"
className={`${statusConfig.icon} ${statusConfig.textClass} mr-2 text-xs`}
/>
<span
key="status-text"
className={`${statusConfig.textClass} text-xs font-medium`}
>
{statusConfig.label}
</span>
</div>
<div key="date">{phase.date}</div>
<i
key="expand-icon"
className={`fas fa-chevron-${
isExpanded ? "up" : "down"
} text-gray-400 text-sm`}
/>
</div>
</div>
{isExpanded && (
<div
key="features-section"
className="mt-6 pt-6 border-t border-gray-700/30"
>
<h5
key="features-title"
className="text-primary font-medium mb-4 flex items-center"
>
<i
key="features-icon"
className="fas fa-list-ul mr-2 text-sm"
/>
Key features:
</h5>
<div
key="features-grid"
className="grid md:grid-cols-2 gap-3"
>
{phase.features.map((feature, featureIndex) => (
<div
key={`feature-${featureIndex}`}
className="flex items-center space-x-3 p-3 bg-custom-bg rounded-lg"
>
<div
className={`w-2 h-2 rounded-full ${statusConfig.textClass.replace(
"text-",
"bg-"
)}`}
/>
<span className="text-secondary text-sm">
{feature}
</span>
</div>
))}
</div>
</div>
)}
</button>
</div>
);
})}
</div>
</div>
</div>
<div key="cta-section" className="mt-12 text-center">
<div
key="cta-card"
className="card-minimal rounded-xl p-8 max-w-2xl mx-auto"
>
<h4
key="cta-title"
className="text-xl font-semibold text-primary mb-3"
>
Join the future of privacy
</h4>
<p key="cta-description" className="text-secondary mb-6">
SecureBit.chat grows thanks to the community. Your ideas and feedback help shape the future of secure communication with complete ASN.1 validation.
</p>
<div
key="cta-buttons"
className="flex flex-col sm:flex-row gap-4 justify-center"
>
<a
key="github-link"
href="https://github.com/SecureBitChat/SecureBitChatBrowserExtension"
className="btn-primary text-white py-3 px-6 rounded-lg font-medium transition-all duration-200 flex items-center justify-center"
>
<i key="github-icon" className="fab fa-github mr-2" />
GitHub Repository
</a>
<a
key="feedback-link"
href="mailto:lockbitchat@tutanota.com"
className="btn-secondary text-white py-3 px-6 rounded-lg font-medium transition-all duration-200 flex items-center justify-center"
>
<i key="feedback-icon" className="fas fa-comments mr-2" />
Feedback
</a>
</div>
</div>
</div>
</div>
);
};
window.Roadmap = Roadmap;

View File

@@ -0,0 +1,59 @@
const SecurityFeatures = () => {
const features = [
{ id: 'feature1', color: '#00ff88', icon: 'fas fa-key accent-green', title: 'ECDH P-384 Key Exchange', desc: 'Military-grade elliptic curve key exchange' },
{ id: 'feature2', color: '#a78bfa', icon: 'fas fa-user-shield accent-purple', title: 'MITM Protection', desc: 'Out-of-band verification against attacks' },
{ id: 'feature3', color: '#ff8800', icon: 'fas fa-lock accent-orange', title: 'AES-GCM 256 Encryption', desc: 'Authenticated encryption standard' },
{ id: 'feature4', color: '#00ffff', icon: 'fas fa-sync-alt accent-cyan', title: 'Perfect Forward Secrecy', desc: 'Automatic key rotation every 5 minutes' },
{ id: 'feature5', color: '#0088ff', icon: 'fas fa-signature accent-blue', title: 'ECDSA P-384 Signatures', desc: 'Digital signatures for message integrity' },
{ id: 'feature6', color: '#f87171', icon: 'fas fa-shield-alt accent-red', title: 'SAS Security', desc: 'Revolutionary key exchange & MITM protection' }
];
React.useEffect(() => {
const cards = document.querySelectorAll(".card");
const radius = 200;
const handleMove = (e) => {
cards.forEach((card) => {
const rect = card.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
const dx = e.clientX - cx;
const dy = e.clientY - cy;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < radius) {
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
card.style.setProperty("--x", `${x}px`);
card.style.setProperty("--y", `${y}px`);
card.classList.add("active-glow");
} else {
card.classList.remove("active-glow");
}
});
};
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
const renderFeature = (f) =>
React.createElement('div', {
key: f.id,
className: "card p-3 sm:p-4 text-center",
style: { "--color": f.color }
}, [
React.createElement('div', { key: 'icon', className: "w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center mx-auto mb-2 sm:mb-3 relative z-10" }, [
React.createElement('i', { className: f.icon })
]),
React.createElement('h4', { key: 'title', className: "text-xs sm:text-sm font-medium text-primary mb-1 relative z-10" }, f.title),
React.createElement('p', { key: 'desc', className: "text-xs text-muted leading-tight relative z-10" }, f.desc)
]);
return React.createElement('div', {
className: "grid grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 max-w-6xl mx-auto mt-8"
}, features.map(renderFeature));
};
window.SecurityFeatures = SecurityFeatures;

View File

@@ -0,0 +1,334 @@
// SessionTimer Component - v4.3.120 - ECDH + DTLS + SAS
const SessionTimer = ({ timeLeft, sessionType, sessionManager, onDisconnect }) => {
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
const [initialized, setInitialized] = React.useState(false);
const [connectionBroken, setConnectionBroken] = React.useState(false);
const [loggedHidden, setLoggedHidden] = React.useState(false);
React.useEffect(() => {
if (connectionBroken) {
if (!loggedHidden) {
console.log('⏱️ SessionTimer initialization skipped - connection broken');
setLoggedHidden(true);
}
return;
}
let initialTime = 0;
if (sessionManager?.hasActiveSession()) {
initialTime = sessionManager.getTimeLeft();
} else if (timeLeft && timeLeft > 0) {
initialTime = timeLeft;
}
if (initialTime <= 0) {
setCurrentTime(0);
setInitialized(false);
setLoggedHidden(true);
return;
}
if (connectionBroken) {
setCurrentTime(0);
setInitialized(false);
setLoggedHidden(true);
return;
}
setCurrentTime(initialTime);
setInitialized(true);
setLoggedHidden(false);
}, [sessionManager, connectionBroken]);
React.useEffect(() => {
if (connectionBroken) {
if (!loggedHidden) {
setLoggedHidden(true);
}
return;
}
if (timeLeft && timeLeft > 0) {
setCurrentTime(timeLeft);
}
setLoggedHidden(false);
}, [timeLeft, connectionBroken]);
React.useEffect(() => {
if (!initialized) {
return;
}
if (connectionBroken) {
if (!loggedHidden) {
setLoggedHidden(true);
}
return;
}
if (!currentTime || currentTime <= 0 || !sessionManager) {
return;
}
const interval = setInterval(() => {
if (connectionBroken) {
setCurrentTime(0);
clearInterval(interval);
return;
}
if (sessionManager?.hasActiveSession()) {
const newTime = sessionManager.getTimeLeft();
setCurrentTime(newTime);
if (window.DEBUG_MODE && Math.floor(Date.now() / 30000) !== Math.floor((Date.now() - 1000) / 30000)) {
console.log('⏱️ Timer tick:', Math.floor(newTime / 1000) + 's');
}
if (newTime <= 0) {
setShowExpiredMessage(true);
setTimeout(() => setShowExpiredMessage(false), 5000);
clearInterval(interval);
}
} else {
setCurrentTime(0);
clearInterval(interval);
}
}, 1000);
return () => {
clearInterval(interval);
};
}, [initialized, currentTime, sessionManager, connectionBroken]);
React.useEffect(() => {
const handleSessionTimerUpdate = (event) => {
if (connectionBroken) {
return;
}
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
setCurrentTime(event.detail.timeLeft);
}
};
const handleForceHeaderUpdate = (event) => {
if (connectionBroken) {
return;
}
if (sessionManager && sessionManager.hasActiveSession()) {
const newTime = sessionManager.getTimeLeft();
setCurrentTime(newTime);
} else {
setCurrentTime(event.detail.timeLeft);
}
};
const handlePeerDisconnect = (event) => {
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
setLoggedHidden(false);
};
const handleNewConnection = (event) => {
setConnectionBroken(false);
setLoggedHidden(false);
};
const handleConnectionCleaned = (event) => {
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
setInitialized(false);
setLoggedHidden(false);
};
const handleSessionReset = (event) => {
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
setInitialized(false);
setLoggedHidden(false);
};
const handleSessionCleanup = (event) => {
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
setInitialized(false);
setLoggedHidden(false);
};
const handleDisconnected = (event) => {
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
setInitialized(false);
setLoggedHidden(false);
};
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
document.addEventListener('force-header-update', handleForceHeaderUpdate);
document.addEventListener('peer-disconnect', handlePeerDisconnect);
document.addEventListener('new-connection', handleNewConnection);
document.addEventListener('connection-cleaned', handleConnectionCleaned);
document.addEventListener('session-reset', handleSessionReset);
document.addEventListener('session-cleanup', handleSessionCleanup);
document.addEventListener('disconnected', handleDisconnected);
return () => {
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
document.removeEventListener('new-connection', handleNewConnection);
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
document.removeEventListener('session-reset', handleSessionReset);
document.removeEventListener('session-cleanup', handleSessionCleanup);
document.removeEventListener('disconnected', handleDisconnected);
};
}, [sessionManager]);
if (showExpiredMessage) {
return React.createElement('div', {
className: 'session-timer expired flex items-center space-x-2 px-3 py-1.5 rounded-lg animate-pulse',
style: { background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%)' }
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-exclamation-triangle text-red-400'
}),
React.createElement('span', {
key: 'message',
className: 'text-red-400 text-sm font-medium'
}, 'Session Expired!')
]);
}
if (!sessionManager) {
if (!loggedHidden) {
console.log('⏱️ SessionTimer hidden - no sessionManager');
setLoggedHidden(true);
}
return null;
}
if (connectionBroken) {
if (!loggedHidden) {
console.log('⏱️ SessionTimer hidden - connection broken');
setLoggedHidden(true);
}
return null;
}
if (!currentTime || currentTime <= 0) {
if (!loggedHidden) {
console.log('⏱️ SessionTimer hidden - no time left, currentTime:', currentTime);
setLoggedHidden(true);
}
return null;
}
if (loggedHidden) {
setLoggedHidden(false);
}
const totalMinutes = Math.floor(currentTime / (60 * 1000));
const totalSeconds = Math.floor(currentTime / 1000);
const isDemo = sessionType === 'demo';
const isWarning = isDemo ? totalMinutes <= 2 : totalMinutes <= 10;
const isCritical = isDemo ? totalSeconds <= 60 : totalMinutes <= 5;
const formatTime = (ms) => {
const hours = Math.floor(ms / (60 * 60 * 1000));
const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000));
const seconds = Math.floor((ms % (60 * 1000)) / 1000);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
};
const getTimerStyle = () => {
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
const timeProgress = (totalDuration - currentTime) / totalDuration;
let backgroundColor, textColor, iconColor, iconClass, shouldPulse;
if (timeProgress <= 0.33) {
backgroundColor = 'linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(22, 163, 74, 0.15) 100%)';
textColor = 'text-green-400';
iconColor = 'text-green-400';
iconClass = 'fas fa-clock';
shouldPulse = false;
} else if (timeProgress <= 0.66) {
backgroundColor = 'linear-gradient(135deg, rgba(234, 179, 8, 0.15) 0%, rgba(202, 138, 4, 0.15) 100%)';
textColor = 'text-yellow-400';
iconColor = 'text-yellow-400';
iconClass = 'fas fa-clock';
shouldPulse = false;
} else {
backgroundColor = 'linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(220, 38, 38, 0.15) 100%)';
textColor = 'text-red-400';
iconColor = 'text-red-400';
iconClass = 'fas fa-exclamation-triangle';
shouldPulse = true;
}
return { backgroundColor, textColor, iconColor, iconClass, shouldPulse };
};
const timerStyle = getTimerStyle();
const handleTimerClick = () => {
if (onDisconnect && typeof onDisconnect === 'function') {
onDisconnect();
}
};
return React.createElement('div', {
className: `session-timer flex items-center space-x-2 px-3 py-1.5 rounded-lg transition-all duration-500 cursor-pointer hover:opacity-80 ${
isDemo ? 'demo-session' : ''
} ${timerStyle.shouldPulse ? 'animate-pulse' : ''}`,
style: { background: timerStyle.backgroundColor },
onClick: handleTimerClick,
title: 'Click to disconnect and clear session'
}, [
React.createElement('i', {
key: 'icon',
className: `${timerStyle.iconClass} ${timerStyle.iconColor}`
}),
React.createElement('span', {
key: 'time',
className: `text-sm font-mono font-semibold ${timerStyle.textColor}`
}, formatTime(currentTime)),
React.createElement('div', {
key: 'progress',
className: 'ml-2 w-16 h-1 bg-gray-700 rounded-full overflow-hidden'
}, [
React.createElement('div', {
key: 'progress-bar',
className: `${timerStyle.textColor.replace('text-', 'bg-')} h-full rounded-full transition-all duration-500`,
style: {
width: `${Math.max(0, Math.min(100, (currentTime / (sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000)) * 100))}%`
}
})
])
]);
};
window.SessionTimer = SessionTimer;
window.updateSessionTimer = (newTimeLeft, newSessionType) => {
document.dispatchEvent(new CustomEvent('session-timer-update', {
detail: { timeLeft: newTimeLeft, sessionType: newSessionType }
}));
};

View File

@@ -0,0 +1,123 @@
const Testimonials = () => {
const testimonials = [
{ id: "t1", rating: 5.0, text: "The interface feels modern and smooth. It saves me at least 2 hours every day when managing design tasks."},
{ id: "t2", rating: 5.0, text: "Finally, a solution that blends speed with simplicity. My team adopted it within a week without training."},
{ id: "t3", rating: 5.0, text: "I can track progress in real time and get a clear overview of our workflow. It feels empowering."},
{ id: "t4", rating: 5.0, text: "Our pipeline visibility improved dramatically. I no longer need to manually track updates."},
{ id: "t5", rating: 5.0, text: "The security-first approach gives me peace of mind. We handle sensitive data with confidence now."},
{ id: "t6", rating: 5.0, text: "User feedback cycles are now twice as fast. It helps us test and ship features quickly."}
];
React.useEffect(() => {
const colUp = document.querySelector(".col-up");
const colDown = document.querySelector(".col-down");
const wrapper = document.querySelector(".testimonials-wrapper");
if (!colUp || !colDown || !wrapper) return;
let paused = false;
const speed = 0.5;
let animationId;
const cloneCards = (container) => {
const cards = Array.from(container.children);
cards.forEach(card => {
const clone = card.cloneNode(true);
container.appendChild(clone);
});
};
cloneCards(colUp);
cloneCards(colDown);
const getHalfHeight = (el) => {
const children = Array.from(el.children);
const halfCount = children.length / 2;
let height = 0;
for (let i = 0; i < halfCount; i++) {
height += children[i].offsetHeight;
if (i < halfCount - 1) height += 24;
}
return height;
};
let y1 = 0;
const maxScroll1 = getHalfHeight(colUp);
const maxScroll2 = getHalfHeight(colDown);
let y2 = -maxScroll2;
function animate() {
if (!paused) {
y1 -= speed;
y2 += speed;
if (Math.abs(y1) >= maxScroll1) {
y1 = 0;
}
if (y2 >= 0) {
y2 = -maxScroll2;
}
colUp.style.transform = `translateY(${y1}px)`;
colDown.style.transform = `translateY(${y2}px)`;
}
animationId = requestAnimationFrame(animate);
}
animate();
const handleMouseEnter = () => { paused = true; };
const handleMouseLeave = () => { paused = false; };
wrapper.addEventListener("mouseenter", handleMouseEnter);
wrapper.addEventListener("mouseleave", handleMouseLeave);
return () => {
cancelAnimationFrame(animationId);
wrapper.removeEventListener("mouseenter", handleMouseEnter);
wrapper.removeEventListener("mouseleave", handleMouseLeave);
};
}, []);
const renderCard = (t, index) => (
<div key={`${t.id}-${index}`} className="card bg-neutral-900 rounded-xl p-5 shadow-md w-72 text-sm text-white flex-shrink-0">
<div className="flex items-center mb-2 text-yellow-400">
{"★".repeat(Math.floor(t.rating))}
<span className="ml-2 text-secondary">{t.rating.toFixed(1)}</span>
</div>
<p className="text-secondary mb-3">{t.text}</p>
</div>
);
return (
<section className="py-14 px-6 bg-transparent">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-12 max-w-7xl mx-auto items-center">
<div className="lg:col-span-2 flex flex-col justify-center">
<p className="text-sm text-secondary mb-2">Testimonials</p>
<h2 className="text-2xl sm:text-3xl font-bold text-white mb-4 leading-snug">
What our users are saying
</h2>
<p className="text-secondary text-sm">
We continuously listen to our community and improve every day.
</p>
</div>
<div className="lg:col-span-3 testimonials-wrapper flex gap-6 overflow-hidden relative h-[420px]">
<div className="pointer-events-none absolute top-0 left-0 w-full h-16 bg-gradient-to-b from-[#1f1f1f]/90 to-transparent z-20"></div>
<div className="pointer-events-none absolute bottom-0 left-0 w-full h-16 bg-gradient-to-t from-[#1f1f1f]/90 to-transparent z-20"></div>
<div className="col-up flex flex-col gap-6">
{testimonials.map((t, i) => renderCard(t, i))}
</div>
<div className="col-down flex flex-col gap-6">
{testimonials.map((t, i) => renderCard(t, i))}
</div>
</div>
</div>
</section>
);
};
window.Testimonials = Testimonials;

View File

@@ -0,0 +1,209 @@
// Enhanced Modern Slider Component with Loading Protection
const UniqueFeatureSlider = () => {
const trackRef = React.useRef(null);
const wrapRef = React.useRef(null);
const [current, setCurrent] = React.useState(0);
const [isReady, setIsReady] = React.useState(false);
const slides = [
{
icon: "🛡️",
bgImage: "linear-gradient(135deg, rgb(255 107 53 / 6%) 0%, rgb(255 140 66 / 45%) 100%)",
thumbIcon: "🔒",
title: "18-Layer Military Security",
description: "Revolutionary defense system with ECDH P-384 + AES-GCM 256 + ECDSA + Complete ASN.1 Validation."
},
{
icon: "🌐",
bgImage: "linear-gradient(135deg, rgb(147 51 234 / 6%) 0%, rgb(168 85 247 / 45%) 100%)",
thumbIcon: "🔗",
title: "Pure P2P WebRTC",
description: "Direct peer-to-peer connections without any servers. Complete decentralization with zero infrastructure."
},
{
icon: "🔄",
bgImage: "linear-gradient(135deg, rgb(16 185 129 / 6%) 0%, rgb(52 211 153 / 45%) 100%)",
thumbIcon: "⚡",
title: "Perfect Forward Secrecy",
description: "Automatic key rotation every 5 minutes. Non-extractable keys with hardware protection."
},
{
icon: "🎭",
bgImage: "linear-gradient(135deg, rgb(6 182 212 / 6%) 0%, rgb(34 211 238 / 45%) 100%)",
thumbIcon: "🌫️",
title: "Traffic Obfuscation",
description: "Fake traffic generation and pattern masking make communication indistinguishable from noise."
},
{
icon: "👁️",
bgImage: "linear-gradient(135deg, rgb(37 99 235 / 6%) 0%, rgb(59 130 246 / 45%) 100%)",
thumbIcon: "🚫",
title: "Zero Data Collection",
description: "No registration, no servers, no logs. Complete anonymity with instant channels."
}
];
// Проверка готовности компонента
React.useEffect(() => {
const timer = setTimeout(() => {
setIsReady(true);
}, 100);
return () => clearTimeout(timer);
}, []);
const isMobile = () => window.matchMedia("(max-width:767px)").matches;
const center = React.useCallback((i) => {
if (!trackRef.current || !wrapRef.current) return;
const card = trackRef.current.children[i];
if (!card) return;
const axis = isMobile() ? "top" : "left";
const size = isMobile() ? "clientHeight" : "clientWidth";
const start = isMobile() ? card.offsetTop : card.offsetLeft;
wrapRef.current.scrollTo({
[axis]: start - (wrapRef.current[size] / 2 - card[size] / 2),
behavior: "smooth"
});
}, []);
const activate = React.useCallback((i, scroll = false) => {
if (i === current) return;
setCurrent(i);
if (scroll) {
setTimeout(() => center(i), 50);
}
}, [current, center]);
const go = (step) => {
const newIndex = Math.min(Math.max(current + step, 0), slides.length - 1);
activate(newIndex, true);
};
React.useEffect(() => {
const handleKeydown = (e) => {
if (["ArrowRight", "ArrowDown"].includes(e.key)) go(1);
if (["ArrowLeft", "ArrowUp"].includes(e.key)) go(-1);
};
window.addEventListener("keydown", handleKeydown, { passive: true });
return () => window.removeEventListener("keydown", handleKeydown);
}, [current]);
React.useEffect(() => {
if (isReady) {
center(current);
}
}, [current, center, isReady]);
// Render loading state if not ready
if (!isReady) {
return React.createElement('section', {
style: {
background: 'transparent',
minHeight: '400px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
},
React.createElement('div', {
style: {
opacity: 0.5,
fontSize: '14px',
color: '#fff'
}
}, 'Loading...')
);
}
return React.createElement('section', { style: { background: 'transparent' } }, [
// Header
React.createElement('div', {
key: 'head',
className: 'head'
}, [
React.createElement('h2', {
key: 'title',
className: 'text-2xl sm:text-3xl font-bold text-white mb-4 leading-snug'
}, 'Why SecureBit.chat is unique'),
React.createElement('div', {
key: 'controls',
className: 'controls'
}, [
React.createElement('button', {
key: 'prev',
id: 'prev-slider',
className: 'nav-btn',
'aria-label': 'Prev',
disabled: current === 0,
onClick: () => go(-1)
}, ''),
React.createElement('button', {
key: 'next',
id: 'next-slider',
className: 'nav-btn',
'aria-label': 'Next',
disabled: current === slides.length - 1,
onClick: () => go(1)
}, '')
])
]),
// Slider
React.createElement('div', {
key: 'slider',
className: 'slider',
ref: wrapRef
},
React.createElement('div', {
className: 'track',
ref: trackRef
}, slides.map((slide, index) =>
React.createElement('article', {
key: index,
className: 'project-card',
...(index === current ? { active: '' } : {}),
onMouseEnter: () => {
if (window.matchMedia("(hover:hover)").matches) {
activate(index, true);
}
},
onClick: () => activate(index, true)
}, [
// Background
React.createElement('div', {
key: 'bg',
className: 'project-card__bg',
style: {
background: slide.bgImage,
backgroundSize: 'cover',
backgroundPosition: 'center'
}
}),
// Content
React.createElement('div', {
key: 'content',
className: 'project-card__content'
}, [
// Text container
React.createElement('div', { key: 'text' }, [
React.createElement('h3', {
key: 'title',
className: 'project-card__title'
}, slide.title),
React.createElement('p', {
key: 'desc',
className: 'project-card__desc'
}, slide.description)
])
])
])
))
),
]);
};
// Export for use in your app
window.UniqueFeatureSlider = UniqueFeatureSlider;

File diff suppressed because it is too large Load Diff

481
src/crypto/cose-qr.js Normal file
View File

@@ -0,0 +1,481 @@
/**
* COSE-based QR Code Compression and Encryption
* Implements secure payload packing with CBOR, compression, and chunking
*/
import * as cbor from 'cbor-js';
import * as pako from 'pako';
import * as base64 from 'base64-js';
// Base64URL encoding/decoding helpers
function toBase64Url(uint8) {
let b64 = base64.fromByteArray(uint8);
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function fromBase64Url(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return base64.toByteArray(str);
}
// Generate UUID for chunking
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Pack secure payload using COSE-like structure with compression
* @param {Object} payloadObj - The data to pack
* @param {CryptoKey} senderEcdsaPrivKey - Sender's signing key (optional)
* @param {CryptoKey} recipientEcdhPubKey - Recipient's ECDH key (optional, null for broadcast)
* @returns {Array<string>} Array of QR code strings (chunks)
*/
export async function packSecurePayload(payloadObj, senderEcdsaPrivKey = null, recipientEcdhPubKey = null) {
try {
console.log('🔐 Starting COSE packing...');
// 1. Canonicalize payload (minified JSON)
const payloadJson = JSON.stringify(payloadObj);
console.log(`📊 Original payload size: ${payloadJson.length} characters`);
// 2. Create ephemeral ECDH keypair (P-384) for encryption
let ciphertextCose;
let ephemeralRaw = null;
if (recipientEcdhPubKey) {
console.log('🔐 Encrypting for specific recipient...');
// Generate ephemeral ECDH keypair
const ecdhPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-384" },
true,
["deriveKey", "deriveBits"]
);
// Export ephemeral public key as raw bytes
ephemeralRaw = new Uint8Array(await crypto.subtle.exportKey('raw', ecdhPair.publicKey));
// Derive shared secret
const sharedBits = await crypto.subtle.deriveBits(
{ name: "ECDH", public: recipientEcdhPubKey },
ecdhPair.privateKey,
384
);
// HKDF-SHA384: derive AES-256-GCM key
const hkdfKey = await crypto.subtle.importKey('raw', sharedBits, 'HKDF', false, ['deriveKey']);
const cek = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-384',
salt: new Uint8Array(0),
info: new TextEncoder().encode('SecureBit QR ECDH AES key')
},
hkdfKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// AES-GCM encrypt payload
const iv = crypto.getRandomValues(new Uint8Array(12));
const enc = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
cek,
new TextEncoder().encode(payloadJson)
);
// Build COSE_Encrypt-like structure
ciphertextCose = {
protected: { alg: 'A256GCM' },
unprotected: { epk: ephemeralRaw },
ciphertext: new Uint8Array(enc),
iv: iv
};
} else {
console.log('🔐 Using broadcast mode (no encryption)...');
// Broadcast mode: not encrypted, include ephemeral key for future use
ephemeralRaw = crypto.getRandomValues(new Uint8Array(97)); // P-384 uncompressed point size
ciphertextCose = {
plaintext: new TextEncoder().encode(payloadJson),
epk: ephemeralRaw
};
}
// 3. Wrap in COSE_Sign1 structure (sign if key provided)
let coseSign1;
const toSign = cbor.encode(ciphertextCose);
if (senderEcdsaPrivKey) {
console.log('🔐 Signing payload...');
// Sign using ECDSA P-384 SHA-384
const signature = new Uint8Array(await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-384' },
senderEcdsaPrivKey,
toSign
));
// COSE_Sign1 as array: [protected, unprotected, payload, signature]
const protectedHeader = cbor.encode({ alg: 'ES384' });
const unprotectedHeader = { kid: 'securebit-sender' };
coseSign1 = [protectedHeader, unprotectedHeader, toSign, signature];
} else {
console.log('🔐 No signing key provided, using unsigned structure...');
// COSE_Sign1 as array: [protected, unprotected, payload, signature]
const protectedHeader = cbor.encode({ alg: 'none' });
const unprotectedHeader = {};
coseSign1 = [protectedHeader, unprotectedHeader, toSign, new Uint8Array(0)];
}
// 4. Final encode: CBOR -> deflate -> base64url
const cborFinal = cbor.encode(coseSign1);
const compressed = pako.deflate(cborFinal);
const encoded = toBase64Url(compressed);
console.log(`📊 Compressed size: ${encoded.length} characters (${Math.round((1 - encoded.length/payloadJson.length) * 100)}% reduction)`);
// 5. Chunking for QR codes - улучшенное разбиение для лучшего сканирования
const TARGET_CHUNKS = 10; // Целевое количество частей
const QR_MAX = Math.max(200, Math.floor(encoded.length / TARGET_CHUNKS)); // Динамический размер части
const chunks = [];
if (encoded.length <= QR_MAX) {
// Single chunk
chunks.push(JSON.stringify({
hdr: { v: 1, id: generateUUID(), seq: 1, total: 1 },
body: encoded
}));
} else {
// Multiple chunks - разбиваем на больше частей для лучшего сканирования
const id = generateUUID();
const totalChunks = Math.ceil(encoded.length / QR_MAX);
console.log(`📊 COSE: Splitting ${encoded.length} chars into ${totalChunks} chunks (max ${QR_MAX} chars per chunk)`);
for (let i = 0, seq = 1; i < encoded.length; i += QR_MAX, seq++) {
const part = encoded.slice(i, i + QR_MAX);
chunks.push(JSON.stringify({
hdr: { v: 1, id, seq, total: totalChunks },
body: part
}));
}
}
console.log(`📊 Generated ${chunks.length} QR chunk(s)`);
return chunks;
} catch (error) {
console.error('❌ Error in packSecurePayload:', error);
throw error;
}
}
/**
* Receive and process COSE-packed QR data
* @param {Array<string>} qrStrings - Array of QR code strings
* @param {CryptoKey} recipientEcdhPrivKey - Recipient's ECDH private key (optional)
* @param {CryptoKey} trustedSenderPubKey - Trusted sender's public key (optional)
* @returns {Array<Object>} Array of processed payloads
*/
export async function receiveAndProcess(qrStrings, recipientEcdhPrivKey = null, trustedSenderPubKey = null) {
try {
console.log('🔓 Starting COSE processing...');
// 1. Assemble chunks by ID
console.log(`📊 Processing ${qrStrings.length} QR string(s)`);
const assembled = await assembleFromQrStrings(qrStrings);
if (!assembled.length) {
console.error('❌ No complete packets found after assembly');
throw new Error('No complete packets found');
}
console.log(`📊 Assembled ${assembled.length} complete packet(s)`);
console.log('📊 First assembled packet:', assembled[0]);
const results = [];
for (const pack of assembled) {
try {
const encoded = pack.jsonObj;
// 2. Decode: base64url -> decompress -> CBOR decode
const compressed = fromBase64Url(encoded.body || encoded);
const cborBytes = pako.inflate(compressed);
console.log('🔓 Decompressed CBOR bytes length:', cborBytes.length);
console.log('🔓 CBOR bytes type:', typeof cborBytes, cborBytes.constructor.name);
// Convert Uint8Array to ArrayBuffer for cbor-js
const cborArrayBuffer = cborBytes.buffer.slice(cborBytes.byteOffset, cborBytes.byteOffset + cborBytes.byteLength);
console.log('🔓 Converted to ArrayBuffer, length:', cborArrayBuffer.byteLength);
const coseSign1 = cbor.decode(cborArrayBuffer);
console.log('🔓 Decoded COSE structure');
// Handle both array and object formats
let protectedHeader, unprotectedHeader, payload, signature;
if (Array.isArray(coseSign1)) {
// Array format: [protected, unprotected, payload, signature]
[protectedHeader, unprotectedHeader, payload, signature] = coseSign1;
console.log('🔓 COSE structure is array format');
} else {
// Object format (legacy)
protectedHeader = coseSign1.protected;
unprotectedHeader = coseSign1.unprotected;
payload = coseSign1.payload;
signature = coseSign1.signature;
console.log('🔓 COSE structure is object format (legacy)');
}
// 3. Verify signature (if key provided)
if (trustedSenderPubKey && signature && signature.length > 0) {
const toVerify = cbor.encode([protectedHeader, unprotectedHeader, payload]);
const isValid = await crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-384' },
trustedSenderPubKey,
signature,
toVerify
);
if (!isValid) {
console.warn('⚠️ Signature verification failed');
continue;
}
console.log('✅ Signature verified');
}
// 4. Decrypt payload
let inner;
if (payload instanceof Uint8Array) {
// Payload is still encoded
const innerArrayBuffer = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
inner = cbor.decode(innerArrayBuffer);
} else {
// Payload is already decoded
inner = payload;
}
console.log('🔓 Inner payload type:', typeof inner, inner.constructor.name);
console.log('🔓 Inner payload keys:', Object.keys(inner));
console.log('🔓 Inner payload full object:', inner);
let payloadObj;
if (inner.ciphertext && recipientEcdhPrivKey) {
console.log('🔓 Decrypting encrypted payload...');
// Get ephemeral public key
const epkRaw = inner.unprotected?.epk || inner.epk;
// Import ephemeral public key
const ephemeralPub = await crypto.subtle.importKey(
'raw',
epkRaw,
{ name: 'ECDH', namedCurve: 'P-384' },
true,
[]
);
// Derive shared secret
const sharedBits = await crypto.subtle.deriveBits(
{ name: 'ECDH', public: ephemeralPub },
recipientEcdhPrivKey,
384
);
// HKDF-SHA384 -> AES key
const hkdfKey = await crypto.subtle.importKey('raw', sharedBits, 'HKDF', false, ['deriveKey']);
const cek = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-384',
salt: new Uint8Array(0),
info: new TextEncoder().encode('SecureBit QR ECDH AES key')
},
hkdfKey,
{ name: 'AES-GCM', length: 256 },
true,
['decrypt']
);
// Decrypt
const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: inner.iv },
cek,
inner.ciphertext
);
const payloadJson = new TextDecoder().decode(plaintext);
payloadObj = JSON.parse(payloadJson);
} else if (inner.plaintext) {
console.log('🔓 Processing plaintext payload...');
// Broadcast mode
payloadObj = JSON.parse(new TextDecoder().decode(inner.plaintext));
} else if (Object.keys(inner).length === 0) {
console.log('🔓 Empty inner payload, using alternative approach...');
// Alternative: try to use the original assembled body
try {
const originalBody = encoded.body || encoded;
console.log('🔓 Trying to decode original body:', originalBody.substring(0, 50) + '...');
// Decode base64url -> decompress -> CBOR decode -> extract JSON
const compressed = fromBase64Url(originalBody);
const decompressed = pako.inflate(compressed);
console.log('🔓 Decompressed length:', decompressed.length);
// Convert to ArrayBuffer for CBOR decoding
const decompressedArrayBuffer = decompressed.buffer.slice(decompressed.byteOffset, decompressed.byteOffset + decompressed.byteLength);
const cborDecoded = cbor.decode(decompressedArrayBuffer);
console.log('🔓 CBOR decoded structure:', cborDecoded);
// Handle both array and object formats
let payload;
if (Array.isArray(cborDecoded)) {
// Array format: [protected, unprotected, payload, signature]
console.log('🔓 Alternative: COSE structure is array format');
console.log('🔓 Array length:', cborDecoded.length);
console.log('🔓 Array elements:', cborDecoded.map((el, i) => `${i}: ${typeof el} ${el.constructor.name}`));
// Payload is at index 2
payload = cborDecoded[2];
console.log('🔓 Payload at index 2:', payload);
} else {
// Object format (legacy)
payload = cborDecoded.payload;
console.log('🔓 Alternative: COSE structure is object format (legacy)');
}
// Extract the actual payload from CBOR structure
if (payload && payload instanceof Uint8Array) {
const payloadArrayBuffer = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
const innerCbor = cbor.decode(payloadArrayBuffer);
console.log('🔓 Inner CBOR structure:', innerCbor);
if (innerCbor.plaintext) {
const jsonString = new TextDecoder().decode(innerCbor.plaintext);
payloadObj = JSON.parse(jsonString);
console.log('🔓 Successfully decoded via alternative approach');
console.log('🔓 Alternative payloadObj:', payloadObj);
} else {
console.error('❌ No plaintext found in inner CBOR structure');
continue;
}
} else if (payload && typeof payload === 'object' && Object.keys(payload).length > 0) {
// Payload is already a decoded object
console.log('🔓 Payload is already decoded object:', payload);
if (payload.plaintext) {
const jsonString = new TextDecoder().decode(payload.plaintext);
payloadObj = JSON.parse(jsonString);
console.log('🔓 Successfully decoded from payload object');
console.log('🔓 Alternative payloadObj:', payloadObj);
} else {
console.error('❌ No plaintext found in payload object');
continue;
}
} else {
console.error('❌ No payload found in CBOR structure');
console.log('🔓 CBOR structure keys:', Object.keys(cborDecoded));
console.log('🔓 Payload type:', typeof payload);
console.log('🔓 Payload value:', payload);
continue;
}
} catch (altError) {
console.error('❌ Alternative approach failed:', altError);
continue;
}
} else {
console.warn('⚠️ Unknown payload format:', inner);
continue;
}
results.push({
payloadObj,
senderVerified: !!trustedSenderPubKey,
encrypted: !!inner.ciphertext
});
} catch (packError) {
console.error('❌ Error processing packet:', packError);
continue;
}
}
console.log(`✅ Successfully processed ${results.length} payload(s)`);
return results;
} catch (error) {
console.error('❌ Error in receiveAndProcess:', error);
throw error;
}
}
/**
* Assemble QR chunks into complete packets
* @param {Array<string>} qrStrings - Array of QR code strings
* @returns {Array<Object>} Array of assembled packets
*/
async function assembleFromQrStrings(qrStrings) {
const packets = new Map();
console.log('🔧 Starting assembly of QR strings...');
for (const qrString of qrStrings) {
try {
console.log('🔧 Parsing QR string:', qrString.substring(0, 100) + '...');
const parsed = JSON.parse(qrString);
console.log('🔧 Parsed structure:', parsed);
if (parsed.hdr && parsed.body) {
const id = parsed.hdr.id;
console.log(`🔧 Processing packet ID: ${id}, seq: ${parsed.hdr.seq}, total: ${parsed.hdr.total}`);
if (!packets.has(id)) {
packets.set(id, {
id: id,
chunks: new Map(),
total: parsed.hdr.total
});
console.log(`🔧 Created new packet for ID: ${id}`);
}
const packet = packets.get(id);
packet.chunks.set(parsed.hdr.seq, parsed.body);
console.log(`🔧 Added chunk ${parsed.hdr.seq} to packet ${id}. Current chunks: ${packet.chunks.size}/${packet.total}`);
// Check if complete
if (packet.chunks.size === packet.total) {
console.log(`🔧 Packet ${id} is complete! Assembling body...`);
// Assemble body
let assembledBody = '';
for (let i = 1; i <= packet.total; i++) {
assembledBody += packet.chunks.get(i);
}
packet.jsonObj = { body: assembledBody };
packet.complete = true;
console.log(`🔧 Assembled body length: ${assembledBody.length} characters`);
}
} else {
console.warn('⚠️ QR string missing hdr or body:', parsed);
}
} catch (error) {
console.warn('⚠️ Failed to parse QR string:', error);
continue;
}
}
// Return only complete packets
const completePackets = Array.from(packets.values()).filter(p => p.complete);
console.log(`🔧 Assembly complete. Found ${completePackets.length} complete packets`);
return completePackets;
}
// Export for global use
window.packSecurePayload = packSecurePayload;
window.receiveAndProcess = receiveAndProcess;

File diff suppressed because it is too large Load Diff

34
src/scripts/app-boot.js Normal file
View File

@@ -0,0 +1,34 @@
import { EnhancedSecureCryptoUtils } from '../crypto/EnhancedSecureCryptoUtils.js';
import { EnhancedSecureWebRTCManager } from '../network/EnhancedSecureWebRTCManager.js';
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
// Import UI components (side-effect: they attach themselves to window.*)
import '../components/ui/SessionTimer.jsx';
import '../components/ui/Header.jsx';
import '../components/ui/DownloadApps.jsx';
import '../components/ui/UniqueFeatureSlider.jsx';
import '../components/ui/SecurityFeatures.jsx';
import '../components/ui/Testimonials.jsx';
import '../components/ui/ComparisonTable.jsx';
import '../components/ui/Roadmap.jsx';
import '../components/ui/FileTransfer.jsx';
// Expose to global for legacy usage inside app code
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
// Mount application once DOM and modules are ready
const start = () => {
if (typeof window.initializeApp === 'function') {
window.initializeApp();
} else if (window.DEBUG_MODE) {
console.error('initializeApp is not defined on window');
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}

55
src/scripts/bootstrap-modules.js vendored Normal file
View File

@@ -0,0 +1,55 @@
// Temporary bootstrap that still uses eval for JSX components fetched as text.
// Next step is to replace this with proper ESM imports of prebuilt JS.
(async () => {
try {
const timestamp = Date.now();
const [cryptoModule, webrtcModule, paymentModule, fileTransferModule] = await Promise.all([
import(`../crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
import(`../network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
import(`../session/PayPerSessionManager.js?v=${timestamp}`),
import(`../transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`),
]);
const { EnhancedSecureCryptoUtils } = cryptoModule;
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
const { EnhancedSecureWebRTCManager } = webrtcModule;
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
const { PayPerSessionManager } = paymentModule;
window.PayPerSessionManager = PayPerSessionManager;
const { EnhancedSecureFileTransfer } = fileTransferModule;
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
async function loadReactComponent(path) {
const response = await fetch(`${path}?v=${timestamp}`);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const code = await response.text();
// eslint-disable-next-line no-eval
eval(code);
}
await Promise.all([
loadReactComponent('../components/ui/SessionTimer.jsx'),
loadReactComponent('../components/ui/Header.jsx'),
loadReactComponent('../components/ui/SessionTypeSelector.jsx'),
loadReactComponent('../components/ui/LightningPayment.jsx'),
loadReactComponent('../components/ui/PaymentModal.jsx'),
loadReactComponent('../components/ui/DownloadApps.jsx'),
loadReactComponent('../components/ui/ComparisonTable.jsx'),
loadReactComponent('../components/ui/UniqueFeatureSlider.jsx'),
loadReactComponent('../components/ui/SecurityFeatures.jsx'),
loadReactComponent('../components/ui/Testimonials.jsx'),
loadReactComponent('../components/ui/Roadmap.jsx'),
loadReactComponent('../components/ui/FileTransfer.jsx'),
]);
if (typeof window.initializeApp === 'function') {
window.initializeApp();
} else {
console.error('❌ Function initializeApp not found');
}
} catch (error) {
console.error('❌ Module loading error:', error);
}
})();

44
src/scripts/fa-check.js Normal file
View File

@@ -0,0 +1,44 @@
// Global logging and function settings
window.DEBUG_MODE = true;
// Fake function settings (for stability)
window.DISABLE_FAKE_TRAFFIC = false; // Set true to disable fake messages
window.DISABLE_DECOY_CHANNELS = false; // Set true to disable decoy channels
// Enhanced icon loading fallback
document.addEventListener('DOMContentLoaded', function () {
// Check if Font Awesome loaded properly
function checkFontAwesome() {
const testIcon = document.createElement('i');
testIcon.className = 'fas fa-shield-halved';
testIcon.style.position = 'absolute';
testIcon.style.left = '-9999px';
testIcon.style.visibility = 'hidden';
document.body.appendChild(testIcon);
const computedStyle = window.getComputedStyle(testIcon, '::before');
const content = computedStyle.content;
const fontFamily = computedStyle.fontFamily;
document.body.removeChild(testIcon);
if (!content || content === 'none' || content === 'normal' || (!fontFamily.includes('Font Awesome') && !fontFamily.includes('fa-solid'))) {
console.warn('Font Awesome not loaded properly, using fallback icons');
document.body.classList.add('fa-fallback');
return false;
}
return true;
}
if (!checkFontAwesome()) {
setTimeout(function () {
if (!checkFontAwesome()) {
console.warn('Font Awesome still not loaded, using fallback icons');
document.body.classList.add('fa-fallback');
}
}, 2000);
}
});

193
src/scripts/qr-local.js Normal file
View File

@@ -0,0 +1,193 @@
// Local QR generator and scanner with COSE compression (no external CDNs)
// Exposes:
// - window.generateQRCode(text, { size?: number, margin?: number, errorCorrectionLevel?: 'L'|'M'|'Q'|'H' })
// - window.generateCOSEQRCode(data, senderKey?, recipientKey?) - COSE-based compression
// - window.Html5Qrcode (for scanning QR codes)
// - window.packSecurePayload, window.receiveAndProcess (COSE functions)
import * as QRCode from 'qrcode';
import { Html5Qrcode } from 'html5-qrcode';
import { gzip, ungzip, deflate, inflate } from 'pako';
import * as cbor from 'cbor-js';
import { packSecurePayload, receiveAndProcess } from '../crypto/cose-qr.js';
// Compact payload prefix to signal gzip+base64 content
const COMPRESSION_PREFIX = 'SB1:gz:';
const BINARY_PREFIX = 'SB1:bin:'; // CBOR + deflate + base64url
function uint8ToBase64(bytes) {
let binary = '';
const chunkSize = 0x8000;
for (let i = 0; i < bytes.length; i += chunkSize) {
const chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode.apply(null, chunk);
}
return btoa(binary);
}
function base64ToUint8(b64) {
const binary = atob(b64);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
return bytes;
}
function compressStringToBase64Gzip(text) {
const utf8 = new TextEncoder().encode(text);
const gz = gzip(utf8);
return uint8ToBase64(gz);
}
function decompressBase64GzipToString(b64) {
const gz = base64ToUint8(b64);
const out = ungzip(gz);
return new TextDecoder().decode(out);
}
async function generateQRCode(text, opts = {}) {
const size = opts.size || 512;
const margin = opts.margin ?? 2;
const errorCorrectionLevel = opts.errorCorrectionLevel || 'M';
return await QRCode.toDataURL(text, { width: size, margin, errorCorrectionLevel });
}
// Generate QR with gzip+base64 payload and recognizable prefix for scanners
async function generateCompressedQRCode(text, opts = {}) {
try {
const compressedB64 = compressStringToBase64Gzip(text);
const payload = COMPRESSION_PREFIX + compressedB64;
return await generateQRCode(payload, opts);
} catch (e) {
console.warn('generateCompressedQRCode failed, falling back to plain:', e?.message || e);
return await generateQRCode(text, opts);
}
}
// ---- Binary (CBOR) encode/decode helpers ----
function base64ToBase64Url(b64) {
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function base64UrlToBase64(b64url) {
let b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
const pad = b64.length % 4;
if (pad) b64 += '='.repeat(4 - pad);
return b64;
}
function encodeObjectToBinaryBase64Url(obj) {
const cborBytes = cbor.encode(obj);
const compressed = deflate(new Uint8Array(cborBytes));
const b64 = uint8ToBase64(compressed);
return base64ToBase64Url(b64);
}
function decodeBinaryBase64UrlToObject(b64url) {
const b64 = base64UrlToBase64(b64url);
const compressed = base64ToUint8(b64);
const decompressed = inflate(compressed);
const ab = decompressed.buffer.slice(decompressed.byteOffset, decompressed.byteOffset + decompressed.byteLength);
return cbor.decode(ab);
}
async function generateBinaryQRCodeFromObject(obj, opts = {}) {
try {
const b64url = encodeObjectToBinaryBase64Url(obj);
const payload = BINARY_PREFIX + b64url;
return await generateQRCode(payload, opts);
} catch (e) {
console.warn('generateBinaryQRCodeFromObject failed, falling back to JSON compressed:', e?.message || e);
const text = JSON.stringify(obj);
return await generateCompressedQRCode(text, opts);
}
}
// COSE-based QR generation for large data
async function generateCOSEQRCode(data, senderKey = null, recipientKey = null) {
try {
console.log('🔐 Generating COSE-based QR code...');
// Pack data using COSE
const chunks = await packSecurePayload(data, senderKey, recipientKey);
if (chunks.length === 1) {
// Single QR code
return await generateQRCode(chunks[0]);
} else {
// Enforce single-QR policy: let caller fallback to template/reference
console.warn(`📊 COSE packing produced ${chunks.length} chunks; falling back to non-COSE strategy`);
throw new Error('COSE QR would require multiple chunks');
}
} catch (error) {
console.error('Error generating COSE QR code:', error);
throw error;
}
}
// Expose functions to global scope
window.generateQRCode = generateQRCode;
window.generateCompressedQRCode = generateCompressedQRCode;
window.generateBinaryQRCodeFromObject = generateBinaryQRCodeFromObject;
window.generateCOSEQRCode = generateCOSEQRCode;
window.Html5Qrcode = Html5Qrcode;
window.packSecurePayload = packSecurePayload;
window.receiveAndProcess = receiveAndProcess;
// Expose helper to transparently decompress scanner payloads
window.decompressIfNeeded = function (scannedText) {
try {
if (typeof scannedText === 'string' && scannedText.startsWith(COMPRESSION_PREFIX)) {
const b64 = scannedText.slice(COMPRESSION_PREFIX.length);
return decompressBase64GzipToString(b64);
}
} catch (e) {
console.warn('decompressIfNeeded failed:', e?.message || e);
}
return scannedText;
};
// Expose helper to get compressed string with prefix for copy/paste flows
window.compressToPrefixedGzip = function (text) {
try {
const payload = String(text || '');
const compressedB64 = compressStringToBase64Gzip(payload);
return COMPRESSION_PREFIX + compressedB64;
} catch (e) {
console.warn('compressToPrefixedGzip failed:', e?.message || e);
return String(text || '');
}
};
// Expose helpers for binary payloads in copy/paste
window.encodeBinaryToPrefixed = function (objOrJson) {
try {
const obj = typeof objOrJson === 'string' ? JSON.parse(objOrJson) : objOrJson;
const b64url = encodeObjectToBinaryBase64Url(obj);
return BINARY_PREFIX + b64url;
} catch (e) {
console.warn('encodeBinaryToPrefixed failed:', e?.message || e);
return typeof objOrJson === 'string' ? objOrJson : JSON.stringify(objOrJson);
}
};
window.decodeAnyPayload = function (scannedText) {
try {
if (typeof scannedText === 'string') {
if (scannedText.startsWith(BINARY_PREFIX)) {
const b64url = scannedText.slice(BINARY_PREFIX.length);
return decodeBinaryBase64UrlToObject(b64url); // returns object
}
if (scannedText.startsWith(COMPRESSION_PREFIX)) {
const s = window.decompressIfNeeded(scannedText);
return s; // returns JSON string
}
// Not prefixed: return as-is
return scannedText;
}
} catch (e) {
console.warn('decodeAnyPayload failed:', e?.message || e);
}
return scannedText;
};
console.log('QR libraries loaded: generateQRCode, generateCompressedQRCode, generateBinaryQRCodeFromObject, Html5Qrcode, COSE functions');

29
src/styles/animations.css Normal file
View File

@@ -0,0 +1,29 @@
/* Smooth Message Scrolling/Appearance*/
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Icon pulsation */
@keyframes iconPulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
/* Pulse for the timer */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* Scroll of logos */
@keyframes walletLogosScroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}

575
src/styles/components.css Normal file
View File

@@ -0,0 +1,575 @@
.minimal-bg {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.hide-scrollbar::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
/* ============================================ */
/* FILE TRANSFER STYLES */
/* ============================================ */
.file-transfer-component {
margin-top: 1rem;
}
@media (max-width: 768px) {
/* Используем dvh для динамической высоты на мобильных */
.mobile-chat-height {
height: calc(100dvh - 64px) !important;
}
/* Кнопка прокрутки на мобильном */
.scroll-to-bottom-mobile {
bottom: 140px !important;
right: 16px !important;
width: 40px !important;
height: 40px !important;
}
}
@media (max-width: 480px) {
.header-minimal {
height: 56px;
}
.mobile-chat-height {
height: calc(100dvh - 56px) !important;
}
}
.file-drop-zone {
border: 2px dashed #4b5563;
border-radius: 12px;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: rgba(55, 65, 81, 0.1);
}
.file-drop-zone:hover {
border-color: #3b82f6;
background: rgba(59, 130, 246, 0.1);
}
.file-drop-zone.drag-over {
border-color: #10b981;
background: rgba(16, 185, 129, 0.1);
transform: scale(1.02);
}
.drop-content {
pointer-events: none;
}
.active-transfers {
max-height: 300px;
overflow-y: auto;
}
.transfer-item {
transition: all 0.2s ease;
}
.transfer-item:hover {
transform: translateY(-1px);
}
.progress-bar {
position: relative;
height: 6px;
background: rgba(75, 85, 99, 0.3);
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
transition: width 0.3s ease;
border-radius: 3px;
}
.progress-text {
position: absolute;
top: -20px;
right: 0;
color: #9ca3af;
}
.file-transfer-section {
border-top: 1px solid rgba(75, 85, 99, 0.1);
}
@media (max-width: 640px) {
.file-drop-zone {
padding: 1.5rem;
}
.transfer-item {
padding: 0.75rem;
}
.progress-text {
font-size: 0.75rem;
}
}
.header-minimal {
background: rgb(35 36 35 / 13%);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
flex-shrink: 0;
height: 64px;
position: sticky;
top: 0;
z-index: 40;
}
.header-minimal .cursor-pointer:hover {
transform: scale(1.05);
transition: transform 0.2s ease;
}
.header-minimal .cursor-pointer:active {
transform: scale(0.95);
}
/* The main chat container takes up the rest of the height. */
.chat-container {
/* display: flex; */
flex-direction: column;
height: calc(100vh - 64px); /* 64px - header height */
min-height: 0;
/* flex: 1; */
}
.chat-messages-area {
flex: 1;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
.chat-input-area {
flex-shrink: 0;
position: sticky;
bottom: 0;
background: rgba(42, 43, 42, 0.95);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(75, 85, 99, 0.2);
}
/* The message container must occupy the entire available height. */
.chat-messages-area > div:first-child {
height: 100%;
overflow-y: auto;
padding: 1rem;
scroll-behavior: smooth;
scroll-padding-bottom: 20px;
scroll-margin-bottom: 20px;
}
/* For mobile devices, take into account the height of the virtual keyboard */
@media (max-width: 768px) {
.chat-container {
height: calc(100vh - 64px);
height: calc(100dvh - 64px); /* dvh to support dynamic height on mobile */
}
}
/* Fix for main application container */
main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.card-minimal {
background: rgba(42, 43, 42, 0.8);
backdrop-filter: blur(16px);
border: 1px solid rgba(75, 85, 99, 0.2);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
/* Selected state override (higher precedence than Tailwind bg-*) */
.card-minimal--selected {
background: rgba(249, 115, 22, 0.15) !important; /* orange-500 @ 0.15 */
border-color: rgba(249, 115, 22, 1) !important; /* border-orange-500 */
box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.3) !important; /* soft ring */
}
/* .card-minimal:hover {
border-color: rgba(249, 115, 22, 0.3);
transform: translateY(-1px);
transition: all 0.2s ease;
} */
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
margin-right: 8px;
}
.status-connected { background: #10b981; }
.status-connecting { background: #6b7280; }
.status-failed { background: #ef4444; }
.status-disconnected { background: #6b7280; }
.status-verifying { background: #9ca3af; }
.security-shield {
background: linear-gradient(135deg, #2A2B2A 0%, #3a3b3a 100%);
border: 1px solid rgba(75, 85, 99, 0.3);
}
.verification-code {
background: rgba(42, 43, 42, 0.8);
border: 1px solid rgba(75, 85, 99, 0.3);
color: #f1f5f9;
font-family: 'Monaco', 'Menlo', monospace;
letter-spacing: 0.1em;
font-size: 1.2em;
padding: 8px 12px;
border-radius: 8px;
text-align: center;
}
.icon-container {
width: 40px;
height: 40px;
background: rgba(42, 43, 42, 0.8);
border: 1px solid rgba(75, 85, 99, 0.3);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.icon-container i {
font-size: 1.25rem;
line-height: 1;
}
.icon-sm { font-size: 0.875rem; }
.icon-md { font-size: 1rem; }
.icon-lg { font-size: 1.125rem; }
.icon-xl { font-size: 1.25rem; }
.icon-2xl { font-size: 1.5rem; }
.step-number {
width: 32px;
height: 32px;
background: linear-gradient(135deg, #f97316, #ea580c);
color: white;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
}
.icon-fallback {
display: inline-block;
width: 1em;
height: 1em;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.fas, .far, .fab {
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
vertical-align: middle;
}
/* Improve icon rendering */
.fas::before, .far::before, .fab::before {
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
}
/* Icon loading fallback */
.icon-loading {
opacity: 0.7;
animation: iconPulse 1.5s ease-in-out infinite;
}
/* Fallback icons content */
.fa-fallback .fas.fa-shield-halved::before { content: "🛡️"; }
.fa-fallback .fas.fa-shield-alt::before { content: "🛡️"; }
.fa-fallback .fas.fa-lock::before { content: "🔒"; }
.fa-fallback .fas.fa-unlock-alt::before { content: "🔓"; }
.fa-fallback .fas.fa-key::before { content: "🔑"; }
.fa-fallback .fas.fa-fingerprint::before { content: "👆"; }
.fa-fallback .fas.fa-exchange-alt::before { content: "🔄"; }
.fa-fallback .fas.fa-plus::before { content: ""; }
.fa-fallback .fas.fa-link::before { content: "🔗"; }
.fa-fallback .fas.fa-paste::before { content: "📋"; }
.fa-fallback .fas.fa-check-circle::before { content: "✅"; }
.fa-fallback .fas.fa-cogs::before { content: "⚙️"; }
.fa-fallback .fas.fa-rocket::before { content: "🚀"; }
.fa-fallback .fas.fa-copy::before { content: "📄"; }
.fa-fallback .fas.fa-check::before { content: "✓"; }
.fa-fallback .fas.fa-times::before { content: "✗"; }
.fa-fallback .fas.fa-exclamation-triangle::before { content: "⚠️"; }
.fa-fallback .fas.fa-info-circle::before { content: ""; }
.fa-fallback .fas.fa-circle::before { content: "●"; }
.fa-fallback .fas.fa-paper-plane::before { content: "📤"; }
.fa-fallback .fas.fa-comments::before { content: "💬"; }
.fa-fallback .fas.fa-signature::before { content: "✍️"; }
.fa-fallback .fas.fa-power-off::before { content: "⏻"; }
.fa-fallback .fas.fa-arrow-left::before { content: "←"; }
.fa-fallback .fas.fa-chevron-down::before { content: "↓"; }
/* Ensure fallback icons are properly sized & use emoji font */
.fa-fallback .fas::before {
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: inherit;
}
/* Icon alignment in buttons */
button i {
vertical-align: middle;
margin-right: 0.5rem;
}
/* Pay-per-session UI - Обновленный трехцветный таймер */
.session-timer {
padding: 8px 16px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
transition: all 0.5s ease;
}
.session-timer:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* Анимация пульсации для красной зоны */
@keyframes timer-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.session-timer.animate-pulse {
animation: timer-pulse 2s ease-in-out infinite;
}
/* Lightning button */
.lightning-button {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
border: 1px solid rgba(245, 158, 11, 0.3);
transition: all 0.3s ease;
}
.lightning-button:hover {
background: linear-gradient(135deg, #d97706 0%, #b45309 100%);
transform: translateY(-2px);
}
.btn-primary {
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
border: 1px solid rgba(249, 115, 22, 0.3);
}
.btn-primary:hover {
background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%);
transform: translateY(-1px);
}
.btn-secondary {
background: linear-gradient(135deg, #2A2B2A 0%, #3a3b3a 100%);
border: 1px solid rgba(75, 85, 99, 0.3);
}
.btn-secondary:hover {
background: linear-gradient(135deg, #3a3b3a 0%, #4a4b4a 100%);
transform: translateY(-1px);
}
.btn-verify {
background: linear-gradient(135deg, #2A2B2A 0%, #3a3b3a 100%);
border: 1px solid rgba(75, 85, 99, 0.3);
}
.btn-verify:hover {
background: linear-gradient(135deg, #3a3b3a 0%, #4a4b4a 100%);
transform: translateY(-1px);
}
/* Wallet logos container & per-wallet filters */
.wallet-logos-container {
display: flex;
align-items: center;
overflow: hidden;
position: relative;
height: 64px;
margin: 20px 0;
width: 100%;
}
.wallet-logos-track {
display: flex;
align-items: center;
gap: 20px;
animation: walletLogosScroll 30s linear infinite;
width: max-content;
}
.wallet-logo {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 48px;
background: rgba(42, 43, 42, 0.8);
border: 1px solid rgba(75, 85, 99, 0.3);
border-radius: 8px;
font-size: 14px;
font-weight: 600;
color: #f1f5f9;
flex-shrink: 0;
transition: all 0.3s ease;
}
.wallet-logo:hover {
border-color: rgba(249, 115, 22, 0.3);
background: rgba(249, 115, 22, 0.1);
transform: translateY(-2px) scale(1.05);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.wallet-logo.bitcoin-lightning { background: transparent; padding: 4px; }
.wallet-logo.bitcoin-lightning img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.impervious { background: transparent; padding: 4px; }
.wallet-logo.impervious img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.strike { background: transparent; padding: 4px; }
.wallet-logo.strike img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.lnbits { background: transparent; padding: 4px; }
.wallet-logo.lnbits img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.lightning-labs { background: transparent; padding: 4px; }
.wallet-logo.lightning-labs img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.atomic { background: transparent; padding: 4px; }
.wallet-logo.atomic img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.breez { background: transparent; padding: 4px; }
.wallet-logo.breez img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.alby { background: transparent; padding: 4px; }
.wallet-logo.alby img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.phoenix { background: transparent; }
.wallet-logo.blixt { background: transparent; }
.wallet-logo.zeus { background: transparent; padding: 4px; }
.wallet-logo.zeus img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.wos { background: transparent; padding: 4px; }
.wallet-logo.wos img { width: 80px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
.wallet-logo.muun { background: transparent; padding: 4px; }
.wallet-logo.muun img { width: 48px; height: 48px; filter: brightness(0) saturate(100%) invert(43%) sepia(8%) saturate(670%) hue-rotate(202deg) brightness(97%) contrast(86%); }
/* Pause animation on hover for logos */
.wallet-logos-container:hover .wallet-logos-track {
animation-play-state: paused;
}
.message-slide {
animation: messageSlideIn 0.3s ease-out;
}
.chat-messages-area .message:last-child {
scroll-margin-bottom: 20px;
}
.chat-messages-area {
scroll-behavior: smooth;
scroll-padding-bottom: 20px;
}
/* Animations */
@keyframes iconPulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes walletLogosScroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Custom scrollbar */
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(156, 163, 175, 0.5);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(156, 163, 175, 0.7);
}
.accent-orange { color: #fb923c !important; }
.accent-green { color: #34d399 !important; }
.accent-red { color: #f87171 !important; }
.accent-yellow { color: #fbbf24 !important; }
.accent-purple { color: #a78bfa !important; }
.accent-gray { color: #9ca3af !important; }
.accent-cyan { color: #22d3ee !important; }
.accent-blue { color: #60a5fa !important; }
/* Ensure icons visible in dark backgrounds */
.text-secondary i {
opacity: 0.8;
}

467
src/styles/main.css Normal file
View File

@@ -0,0 +1,467 @@
/* Basic fonts and colors */
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
body {
background: #2A2B2A;
overflow-x: hidden;
}
/* Basic backgrounds */
.bg-custom-bg {
background-color: rgb(37 38 37) !important;
}
.bg-my{
background-color: rgb(26 26 26);
}
.bg-header {
background-color: rgb(35 35 35) !important;
}
.minimal-bg {
background: linear-gradient(135deg, #1a1a1a 0%, #2A2B2A 100%);
position: relative;
min-height: 100vh;
}
.card-minimal.rounded-xl.p-6.cursor-pointer.group.flex-1.create {
background-color: #60a5fa08;
}
.card-minimal.rounded-xl.p-6.cursor-pointer.group.flex-1.join {
background-color: #34d39908;
}
tr.bg-black-table {
background-color: #1c1c1c;
}
/* Text styles */
.text-primary { color: #f1f5f9; }
.text-secondary { color: #b6b6b6; }
.text-muted { color: #818080; }
/* Accent colors */
.accent-orange { color: #fb923c; }
.accent-green { color: #34d399; }
.accent-red { color: #f87171; }
.accent-yellow { color: #fbbf24; }
.accent-purple { color: #a78bfa; }
.accent-blue { color: #60a5fa; }
.accent-gray { color: #9ca3af; }
.accent-cyan { color: #22d3ee; }
/* Custom scroll */
.custom-scrollbar::-webkit-scrollbar { width: 6px; }
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(42, 43, 42, 0.3);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(75, 85, 99, 0.5);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(75, 85, 99, 0.7);
}
/* Smooth scrolling */
.scroll-smooth {
scroll-behavior: smooth;
}
/* Improved scrolling for messages */
.messages-container {
scroll-behavior: smooth;
scroll-padding-bottom: 20px;
}
/* Enhanced autoscroll for chat */
.chat-messages-area {
scroll-behavior: smooth;
scroll-padding-bottom: 20px;
}
.chat-messages-area > div:first-child {
scroll-behavior: smooth;
scroll-padding-bottom: 20px;
}
/* Smooth scrolling for all message containers */
[class*="chat"] {
scroll-behavior: smooth;
}
/* Media Queries (Mobile/Tablet) */
@media (max-width: 640px) {
.header-minimal { padding: 0 8px; }
.icon-container {
min-width: 32px;
min-height: 32px;
}
.verification-code {
font-size: 0.875rem;
padding: 6px 8px;
}
.header-minimal .max-w-7xl {
padding-left: 8px;
padding-right: 8px;
}
.header-minimal button {
min-width: 32px;
min-height: 32px;
}
}
@media (min-width: 641px) and (max-width: 1024px) {
.header-minimal .max-w-7xl {
padding-left: 16px;
padding-right: 16px;
}
}
.card {
position: relative;
border-radius: 12px;
overflow: hidden;
transition: transform 0.3s ease;
}
.card::before {
content: "";
position: absolute;
inset: 0;
border-radius: 12px;
padding: 2px;
background: radial-gradient(
circle at var(--x, 50%) var(--y, 50%),
var(--color),
transparent 80%
);
opacity: 0;
transition: opacity 0.4s;
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
.card.active-glow::before {
opacity: 1;
}
.card::after {
content: "";
position: absolute;
inset: 3px;
border-radius: 10px;
z-index: 1;
}
:root {
--gap: 1.25rem;
--speed: 0.55s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--closed: 5rem;
--open: 30rem;
--accent: #fb923c;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Inter, sans-serif;
background: #07090d;
color: #c5c7ce;
}
.head {
max-width: 1400px;
margin: auto;
padding: 70px 20px 40px;
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 2rem;
}
.nav-btn {
width: 2.5rem;
height: 2.5rem;
border: none;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
color: #fff;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.3s;
}
.nav-btn:hover {
background: var(--accent);
}
.nav-btn:disabled {
opacity: 0.3;
cursor: default;
}
.slider {
max-width: 1400px;
margin: auto;
overflow: hidden;
}
.controls {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.track {
display: flex;
gap: var(--gap);
align-items: flex-start;
justify-content: center;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
padding-bottom: 40px;
}
.track::-webkit-scrollbar {
display: none;
}
.project-card {
position: relative;
flex: 0 0 var(--closed);
height: 26rem;
border-radius: 1rem;
overflow: hidden;
cursor: pointer;
transition: flex-basis var(--speed), transform var(--speed);
}
.project-card[active] {
flex-basis: var(--open);
transform: translateY(-6px);
box-shadow: 0 15px 12px rgba(0, 0, 0, 0.25);
}
.project-card__bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
filter: brightness(0.75) saturate(75%);
transition: filter 0.3s, transform var(--speed);
}
.project-card:hover .project-card__bg {
filter: brightness(0.9) saturate(100%);
transform: scale(1.06);
}
.project-card__content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.7rem;
padding: 0;
background: linear-gradient(transparent 40%, rgba(0, 0, 0, 0.35) 100%);
z-index: 2;
}
.project-card__title {
color: #fff;
font-weight: 700;
font-size: 1.35rem;
writing-mode: vertical-rl;
transform: rotate(180deg);
}
.project-card__thumb,
.project-card__desc,
.project-card__btn {
display: none;
}
.project-card[active] .project-card__content {
flex-direction: row;
align-items: center;
padding: 1.2rem 2rem;
gap: 1.1rem;
}
.project-card[active] .project-card__title {
writing-mode: horizontal-tb;
transform: none;
font-size: 2.4rem;
}
.project-card[active] .project-card__thumb,
.project-card[active] .project-card__desc,
.project-card[active] .project-card__btn {
display: block;
}
.project-card__thumb {
width: 133px;
height: 269px;
border-radius: 0.45rem;
object-fit: cover;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
}
.project-card__desc {
color: #b6b6b6;
font-size: 1rem;
line-height: 1.4;
max-width: 16rem;
}
@media (max-width: 767px) {
:root {
--closed: 4rem;
--open: 22rem;
}
.head {
padding: 50px 20px 30px;
}
.track {
flex-direction: column;
scroll-snap-type: y mandatory;
align-items: center;
justify-content: flex-start;
padding-bottom: 0;
}
.project-card {
height: 20rem;
}
.project-card__title {
font-size: 1.1rem;
writing-mode: horizontal-tb;
transform: none;
text-align: center;
padding-inline: 0.3rem;
}
.nav-btn {
width: 2rem;
height: 2rem;
font-size: 1.2rem;
}
}
@media (max-width: 767px) {
:root {
--closed: 100%;
--open: 100%;
--gap: 0.8rem;
}
.head {
padding: 30px 15px 20px;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.slider {
padding: 0 15px;
}
.track {
flex-direction: column;
scroll-snap-type: y mandatory;
gap: 0.8rem;
padding-bottom: 20px;
}
.project-card {
height: auto;
min-height: 80px;
flex: 0 0 auto;
width: 100%;
scroll-snap-align: start;
}
.project-card[active] {
min-height: 300px;
transform: none;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.project-card__content {
flex-direction: row;
justify-content: flex-start;
padding: 1rem;
align-items: center;
gap: 1rem;
}
.project-card__title {
writing-mode: horizontal-tb;
transform: none;
font-size: 1.2rem;
margin-right: auto;
}
.project-card__thumb,
.project-card__desc,
.project-card__btn {
display: none;
}
.project-card[active] .project-card__content {
align-items: flex-start;
padding: 1.5rem;
}
.project-card[active] .project-card__title {
font-size: 1.8rem;
margin-bottom: 1rem;
margin-top: 2rem;
}
.project-card[active] .project-card__thumb {
width: 200px;
height: 267px;
border-radius: 0.35rem;
margin-bottom: 1rem;
}
.project-card[active] .project-card__desc {
font-size: 0.95rem;
max-width: 100%;
margin-bottom: 1rem;
}
.project-card[active] .project-card__btn {
align-self: center;
width: 100%;
text-align: center;
padding: 0.7rem;
}
.dots {
display: none;
}
.controls {
width: 100%;
justify-content: space-between;
padding: 0 15px 20px;
}
.nav-btn {
position: static;
transform: none;
}
}

4
src/styles/tw-input.css Normal file
View File

@@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

File diff suppressed because it is too large Load Diff