1
0
Fork 0

remake cases for single project style

This commit is contained in:
koplenov 2026-04-05 06:51:11 +03:00
parent dd2ada14e8
commit 3c92fbddca
3 changed files with 842 additions and 108 deletions

View file

@ -1,94 +1,244 @@
.day1-container { .day1-container {
width: 100%; width: 100%;
height: 100%; min-height: 100vh;
font-family: "Raleway", sans-serif; position: relative;
font-family: 'Share Tech Mono', monospace;
overflow: hidden;
} }
.day1-wallpaper { .day1-wallpaper {
position: fixed;
inset: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
z-index: 0;
filter: brightness(0.35) saturate(0.6);
} }
/* HUD-шапка */
.day1-header { .day1-header {
position: absolute; position: relative;
top: 0; z-index: 10;
left: 0; background: rgba(0, 0, 0, 0.75);
right: 0; border-bottom: 1px solid rgba(0, 255, 255, 0.2);
font-family: "Raleway", sans-serif; padding: 12px 28px;
background: rgba(47, 37, 191, 0.4); display: flex;
padding: 15px; justify-content: space-between;
color: white; align-items: center;
text-align: center; backdrop-filter: blur(4px);
} }
.day1-header-content { .day1-header-content {
display: flex; display: flex;
font-family: Arial, Helvetica, sans-serif; justify-content: space-between;
justify-content: space-around; align-items: center;
width: 100%;
gap: 16px;
} }
.day1-panel { .day1-header-title {
position: absolute; color: #00FFFF;
bottom: 100px; font-family: 'Orbitron', monospace;
left: 50%; font-size: 13px;
transform: translateX(-50%); font-weight: 700;
background: rgba(47, 37, 191, 0.4); letter-spacing: 2px;
padding: 20px; text-shadow: 0 0 14px rgba(0,255,255,0.5);
border-radius: 10px; }
color: white;
.day1-header-sub {
color: #888;
font-size: 12px;
}
/* Легенда */
.day1-story {
position: relative;
z-index: 10;
max-width: 700px;
margin: 28px auto 0;
padding: 14px 24px;
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(0, 255, 255, 0.15);
border-radius: 8px;
font-size: 13px;
line-height: 1.7;
color: #aaa;
text-align: center; text-align: center;
min-width: 300px; backdrop-filter: blur(4px);
}
/* Прогресс-бар версий */
.day1-progress {
position: relative;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
gap: 0;
margin: 28px auto 0;
max-width: 480px;
}
.day1-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.day1-step-dot {
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid #444;
background: #111;
transition: all 0.3s;
}
.day1-step.active .day1-step-dot {
border-color: #00FFFF;
background: #00FFFF;
box-shadow: 0 0 10px rgba(0,255,255,0.6);
}
.day1-step.done .day1-step-dot {
border-color: #00FF41;
background: #00FF41;
box-shadow: 0 0 8px rgba(0,255,65,0.5);
}
.day1-step-label {
font-size: 11px;
color: #555;
font-family: 'Share Tech Mono', monospace;
white-space: nowrap;
}
.day1-step.active .day1-step-label { color: #00FFFF; }
.day1-step.done .day1-step-label { color: #00FF41; }
.day1-step-line {
flex: 1;
height: 2px;
background: #222;
margin: 0 8px;
margin-bottom: 22px;
transition: background 0.3s;
min-width: 60px;
}
.day1-step-line.done { background: #00FF41; }
/* Панель управления */
.day1-panel {
position: relative;
z-index: 10;
margin: 32px auto 0;
max-width: 440px;
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 10px;
padding: 24px 28px;
text-align: center;
backdrop-filter: blur(6px);
box-shadow: 0 0 30px rgba(0,255,255,0.06);
} }
.day1-version-info { .day1-version-info {
margin-bottom: 15px; margin-bottom: 8px;
color: #888;
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
.day1-version-name {
font-family: 'Orbitron', monospace;
font-size: 18px;
font-weight: 700;
color: #00FFFF;
text-shadow: 0 0 16px rgba(0,255,255,0.4);
margin-bottom: 20px;
} }
.day1-buttons { .day1-buttons {
display: flex; display: flex;
gap: 10px; gap: 12px;
justify-content: center; justify-content: center;
flex-wrap: wrap;
} }
.day1-update-btn { .day1-update-btn {
padding: 10px 20px; padding: 11px 24px;
background: #2a0c84; background: rgba(0, 255, 65, 0.1);
color: white; color: #00FF41;
border: none; border: 1px solid #00FF41;
border-radius: 5px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-family: 'Orbitron', monospace;
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
transition: all 0.2s;
} }
.day1-update-btn:hover:not(:disabled) { .day1-update-btn:hover:not(:disabled) {
background: #4313a3; background: rgba(0, 255, 65, 0.2);
box-shadow: 0 0 16px rgba(0,255,65,0.35);
} }
.day1-update-btn:disabled { .day1-update-btn:disabled {
opacity: 0.6; opacity: 0.4;
cursor: not-allowed; cursor: not-allowed;
} }
.day1-end-btn { .day1-end-btn {
padding: 10px 20px; padding: 11px 24px;
background: #e0af0f; background: rgba(245, 166, 35, 0.1);
color: white; color: #f5a623;
border: none; border: 1px solid #f5a623;
border-radius: 5px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-family: 'Orbitron', monospace;
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
transition: all 0.2s;
} }
.day1-end-btn:hover { .day1-end-btn:hover {
background: #f38518; background: rgba(245, 166, 35, 0.2);
box-shadow: 0 0 16px rgba(245,166,35,0.3);
} }
/* Лоадер обновления */
.day1-loading-bar {
margin-top: 16px;
height: 3px;
background: rgba(0,255,255,0.1);
border-radius: 2px;
overflow: hidden;
}
.day1-loading-fill {
height: 100%;
background: #00FFFF;
border-radius: 2px;
animation: day1-load 2s linear forwards;
box-shadow: 0 0 8px rgba(0,255,255,0.6);
}
@keyframes day1-load {
from { width: 0%; }
to { width: 100%; }
}
/* Модалка ошибки */
.day1-modal-overlay { .day1-modal-overlay {
position: fixed; position: fixed;
top: 0; inset: 0;
left: 0; background: rgba(0, 0, 0, 0.75);
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -96,31 +246,48 @@
} }
.day1-modal { .day1-modal {
background: white; background: #0d0d22;
padding: 20px; border: 1px solid #FF0040;
border-radius: 10px; border-radius: 10px;
padding: 28px 32px;
text-align: center; text-align: center;
max-width: 400px; max-width: 420px;
box-shadow: 0 0 30px rgba(255,0,64,0.2);
}
.day1-modal-icon {
font-size: 36px;
margin-bottom: 12px;
} }
.day1-modal-title { .day1-modal-title {
color: #f44336; color: #FF0040;
margin-bottom: 10px; font-family: 'Orbitron', monospace;
font-size: 15px;
font-weight: 700;
margin-bottom: 12px;
} }
.day1-modal-text { .day1-modal-text {
color: #aaa;
font-size: 13px;
line-height: 1.6;
margin-bottom: 20px; margin-bottom: 20px;
} }
.day1-modal-btn { .day1-modal-btn {
padding: 10px 20px; padding: 10px 28px;
background: #4caf50; background: transparent;
color: white; border: 1px solid rgba(0,255,255,0.3);
border: none; color: #888;
border-radius: 5px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-family: 'Share Tech Mono', monospace;
font-size: 13px;
transition: all 0.2s;
} }
.day1-modal-btn:hover { .day1-modal-btn:hover {
background: #45a049; border-color: #00FFFF;
color: #00FFFF;
} }

View file

@ -6,90 +6,102 @@ interface Case1DesktopProps {
onComplete: (type: WallpaperType) => void; onComplete: (type: WallpaperType) => void;
} }
const VERSIONS = [
{ v: 1, name: 'Windows XP', img: '/images/windows_xp.jpg', type: 'xp' as WallpaperType },
{ v: 2, name: 'Windows 7', img: '/images/windows_7.jpg', type: 'win7' as WallpaperType },
{ v: 3, name: 'Windows 10', img: '/images/windows_10.jpg', type: 'win10' as WallpaperType },
];
const Case1Desktop: React.FC<Case1DesktopProps> = ({ onComplete }) => { const Case1Desktop: React.FC<Case1DesktopProps> = ({ onComplete }) => {
const [version, setVersion] = useState(1); const [version, setVersion] = useState(1);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const getImage = () => { const current = VERSIONS[version - 1];
if (version === 1) return '/images/windows_xp.jpg';
if (version === 2) return '/images/windows_7.jpg';
return '/images/windows_10.jpg';
};
const getVersionName = () => {
if (version === 1) return 'Windows XP';
if (version === 2) return 'Windows 7';
return 'Windows 10';
};
const getWallpaperType = (): WallpaperType => {
if (version === 1) return 'xp';
if (version === 2) return 'win7';
return 'win10';
};
const updateSystem = () => { const updateSystem = () => {
if (version === 1) { if (version >= 3) return;
setLoading(true); setLoading(true);
setTimeout(() => { setTimeout(() => {
setVersion(2); setVersion(v => v + 1);
setLoading(false); setLoading(false);
}, 2000); }, 2000);
} else if (version === 2) {
setLoading(true);
setTimeout(() => {
setVersion(3);
setLoading(false);
}, 2000);
}
}; };
const endDay = () => { const endDay = () => {
if (version === 3) { if (version === 3) {
onComplete(getWallpaperType()); onComplete(current.type);
} else { } else {
setError(`Ошибка! Нельзя завершить день на старой версии ${getVersionName()}. Нужно обновиться до Windows 10`); setError(`Нельзя завершить кейс на ${current.name}. Устаревшая ОС содержит незакрытые уязвимости — обновите систему до Windows 10.`);
} }
}; };
return ( return (
<div className="day1-container"> <div className="day1-container">
<img src={getImage()} alt="wallpaper" className="day1-wallpaper" /> <img src={current.img} alt="wallpaper" className="day1-wallpaper" />
{/* HUD-шапка */}
<div className="day1-header"> <div className="day1-header">
<div className="day1-header-content"> <div className="day1-header-content">
<span>Кейс 1: Обновление системы</span> <span className="day1-header-title">КЕЙС 1 ОБНОВЛЕНИЕ СИСТЕМЫ</span>
<span>Выберите наиболее подходящую версию Windows</span> <span className="day1-header-sub">Устаревшее ПО = открытая дверь для атак</span>
</div> </div>
</div> </div>
<div className="day1-panel"> {/* Легенда */}
<div className="day1-version-info"> <div className="day1-story">
<strong>Текущая версия:</strong> {getVersionName()} Ваша рабочая станция работает на <b style={{ color: '#f5a623' }}>{current.name}</b>.
Производитель прекратил поддержку этой версии патчи безопасности больше не выходят.
<br />
Злоумышленники активно эксплуатируют известные уязвимости необновлённых систем.
<b style={{ color: '#00FF41' }}> Обновите ОС до актуальной версии, чтобы закрыть бреши.</b>
</div> </div>
{/* Прогресс */}
<div className="day1-progress">
{VERSIONS.map((ver, i) => (
<React.Fragment key={ver.v}>
<div className={`day1-step ${version === ver.v ? 'active' : version > ver.v ? 'done' : ''}`}>
<div className="day1-step-dot" />
<span className="day1-step-label">{ver.name}</span>
</div>
{i < VERSIONS.length - 1 && (
<div className={`day1-step-line ${version > ver.v ? 'done' : ''}`} />
)}
</React.Fragment>
))}
</div>
{/* Панель управления */}
<div className="day1-panel">
<div className="day1-version-info">Текущая версия</div>
<div className="day1-version-name">{current.name}</div>
<div className="day1-buttons"> <div className="day1-buttons">
{version !== 3 && ( {version < 3 && (
<button <button className="day1-update-btn" onClick={updateSystem} disabled={loading}>
className="day1-update-btn" {loading ? '⟳ Обновление...' : `↑ Обновить систему`}
onClick={updateSystem}
disabled={loading}
>
{loading ? 'Обновление...' : 'Обновить до Windows 10'}
</button> </button>
)} )}
<button className="day1-end-btn" onClick={endDay}> <button className="day1-end-btn" onClick={endDay}>
Закончить день 1 {version === 3 ? '✓ Завершить кейс' : '→ Продолжить без обновления'}
</button> </button>
</div> </div>
{loading && (
<div className="day1-loading-bar">
<div className="day1-loading-fill" />
</div>
)}
</div> </div>
{error && ( {error && (
<div className="day1-modal-overlay"> <div className="day1-modal-overlay">
<div className="day1-modal"> <div className="day1-modal">
<h3 className="day1-modal-title">Ошибка</h3> <div className="day1-modal-icon"></div>
<div className="day1-modal-title">Небезопасно</div>
<p className="day1-modal-text">{error}</p> <p className="day1-modal-text">{error}</p>
<button className="day1-modal-btn" onClick={() => setError('')}>OK</button> <button className="day1-modal-btn" onClick={() => setError('')}> Назад</button>
</div> </div>
</div> </div>
)} )}

View file

@ -1,19 +1,574 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import type { WallpaperType } from '../App'; import type { WallpaperType } from '../App';
import MainApp from '../MainApp';
// import './Case2Desktop.css';
interface Case2DesktopProps { interface Case2DesktopProps {
onComplete: (type: WallpaperType) => void; onComplete: (type: WallpaperType) => void;
} }
interface Site {
id: number;
name: string;
domain: string;
https: boolean;
isLegit: boolean;
adLabel?: boolean;
description: string;
version: string;
fileName: string;
fileSize: string;
malwareType: string | null;
malwareDesc: string | null;
}
const SITES: Site[] = [
{
id: 1,
name: 'ТелеЛитр — скачать бесплатно быстро',
domain: 'telelitr-download.net',
https: false,
isLegit: false,
adLabel: true,
description: 'Скачать ТелеЛитр бесплатно! Последняя версия 2024. Быстрая загрузка без регистрации.',
version: 'v2.1.4',
fileName: 'TeleLitrFREE_setup.exe',
fileSize: '48 МБ',
malwareType: 'Троян-шпион',
malwareDesc: 'Файл содержит кейлоггер — программу, которая записывает все нажатия клавиш и передаёт злоумышленнику ваши пароли, переписку и данные карт.',
},
{
id: 2,
name: 'TeleLitr — официальный сайт',
domain: 'telelitr.ru',
https: true,
isLegit: true,
description: 'Официальный сайт мессенджера ТелеЛитр. Безопасные звонки и сообщения с end-to-end шифрованием.',
version: 'v2.1.4',
fileName: 'TeleLitr_Setup_2.1.4.exe',
fileSize: '52 МБ',
malwareType: null,
malwareDesc: null,
},
{
id: 3,
name: 'Скачать ТелеЛитр — все версии',
domain: 'tele-litr.ru',
https: false,
isLegit: false,
adLabel: true,
description: 'ТелеЛитр для Windows, Mac, Android. Все версии. Без вирусов (проверено)!',
version: 'v2.1.3',
fileName: 'telelitr_installer_x64.exe',
fileSize: '61 МБ',
malwareType: 'Майнер криптовалюты',
malwareDesc: 'Вместе с мессенджером устанавливается скрытый майнер, который использует ресурсы вашего компьютера для добычи криптовалюты в пользу злоумышленника.',
},
{
id: 4,
name: 'TeleLitr Download — быстро и бесплатно',
domain: 'telelitr-app.com',
https: false,
isLegit: false,
description: 'Мессенджер ТелеЛитр. Скачать installer. Работает на Windows 7/8/10/11.',
version: 'v2.0.9',
fileName: 'TeleLitr_v209_win.exe',
fileSize: '55 МБ',
malwareType: 'Рекламное ПО (Adware)',
malwareDesc: 'Инсталлятор содержит связку нежелательных программ: меняет стартовую страницу браузера, устанавливает тулбары и показывает навязчивую рекламу.',
},
{
id: 5,
name: 'TeleLitr — мессенджер с шифрованием',
domain: 'telelitr-official.net',
https: true,
isLegit: false,
adLabel: true,
description: 'Защищённый мессенджер. Скачайте официальную версию с нашего сайта.',
version: 'v2.1.4',
fileName: 'TeleLitr_Setup.exe',
fileSize: '50 МБ',
malwareType: 'Фишинговый клиент',
malwareDesc: 'Это поддельный клиент мессенджера. Внешне он идентичен настоящему, но при входе передаёт ваш логин и пароль на сервер злоумышленников.',
},
{
id: 6,
name: 'ТелеЛитр скачать — все платформы',
domain: 'get-telelitr.xyz',
https: false,
isLegit: false,
description: 'Скачать ТелеЛитр для Windows. Версия 2024. Установка за 1 минуту.',
version: 'v2.1.1',
fileName: 'get_telelitr.exe',
fileSize: '43 МБ',
malwareType: 'Программа-вымогатель',
malwareDesc: 'После установки вирус шифрует файлы на вашем компьютере и требует выкуп за их восстановление. Домен .xyz — частый признак вредоносных сайтов.',
},
{
id: 7,
name: 'TeleLitr для Windows — скачать',
domain: 'telelitr-win.ru',
https: false,
isLegit: false,
description: 'Мессенджер ТелеЛитр. Версия для Windows. Бесплатно.',
version: 'v2.1.4',
fileName: 'telelitr_windows_setup.exe',
fileSize: '58 МБ',
malwareType: 'Бэкдор',
malwareDesc: 'Программа открывает скрытый удалённый доступ к вашему компьютеру. Злоумышленник может управлять системой, красть файлы и устанавливать другое вредоносное ПО.',
},
{
id: 8,
name: 'ТелеЛитр — мессенджер 2024',
domain: 'telelitrapp.ru',
https: true,
isLegit: false,
description: 'Официальная загрузка. ТелеЛитр 2024 — новый интерфейс, улучшенное шифрование.',
version: 'v3.0.0-beta',
fileName: 'TeleLitr_2024_Pro.exe',
fileSize: '67 МБ',
malwareType: 'Поддельное ПО со шпионажем',
malwareDesc: 'Версия "3.0.0-beta" не существует — это фейк. Программа имитирует мессенджер, но перехватывает все сообщения и звонки, отправляя их третьим лицам.',
},
{
id: 9,
name: 'Скачать TeleLitr — зеркало',
domain: 'mirror-telelitr.com',
https: false,
isLegit: false,
adLabel: true,
description: 'Зеркало официального сайта. Быстрая загрузка без ограничений.',
version: 'v2.1.4',
fileName: 'TeleLitr_mirror.exe',
fileSize: '52 МБ',
malwareType: 'Троян с руткитом',
malwareDesc: '"Зеркала" официальных сайтов — распространённый способ распространения вредоносного ПО. Файл содержит руткит, скрывающий своё присутствие в системе.',
},
{
id: 10,
name: 'TeleLitr — официальный сайт приложения',
domain: 'telelitr.ru',
https: true,
isLegit: true,
description: 'Скачайте ТелеЛитр с официального сайта. Цифровая подпись подтверждена. Безопасно.',
version: 'v2.1.4',
fileName: 'TeleLitr_Setup_2.1.4.exe',
fileSize: '52 МБ',
malwareType: null,
malwareDesc: null,
},
];
const Case2Desktop: React.FC<Case2DesktopProps> = ({ onComplete }) => { const Case2Desktop: React.FC<Case2DesktopProps> = ({ onComplete }) => {
const [searched, setSearched] = useState(false);
const [query, setQuery] = useState('скачать ТелеЛитр мессенджер');
const [selectedSite, setSelectedSite] = useState<Site | null>(null);
const [downloading, setDownloading] = useState(false);
const [modal, setModal] = useState<'malware' | 'success' | null>(null);
const currentUrl = selectedSite ? selectedSite.domain : 'ya.ru';
const isSecure = !selectedSite || selectedSite.https;
const openSite = (site: Site) => setSelectedSite(site);
const handleDownload = () => {
if (!selectedSite) return;
setDownloading(true);
setTimeout(() => {
setDownloading(false);
setModal(selectedSite.isLegit ? 'success' : 'malware');
}, 1800);
};
const reset = () => {
setModal(null);
setSelectedSite(null);
};
return ( return (
<div style={s.root}>
{/* Легенда */}
<div style={s.story}>
<p style={{ margin: 0 }}>
Ваша компания переходит на новый корпоративный мессенджер <b style={{ color: '#00FFFF' }}>ТелеЛитр</b>.
IT-отдел прислал инструкцию: скачайте и установите его самостоятельно.
<br />
<b style={{ color: '#f5a623' }}>Задача:</b> найти официальный сайт и скачать настоящий установщик.
</p>
</div>
{/* Браузер */}
<div style={s.browser}>
<div style={s.chrome}>
{selectedSite && <button style={s.navBtn} onClick={reset}></button>}
<div style={s.urlBar}>
<span style={isSecure ? s.httpsIcon : s.httpIcon}>{isSecure ? '🔒' : '⚠'}</span>
<span style={{ ...s.urlText, color: isSecure ? '#aaa' : '#f5a623' }}>{currentUrl}</span>
{!isSecure && <span style={s.httpWarn}>Небезопасно</span>}
</div>
</div>
<div style={s.content}>
{/* Яндекс главная */}
{!searched && !selectedSite && (
<div style={s.yandexHome}>
<div style={s.yandexLogo}>
<span style={s.yandexY}>Я</span>
<span style={s.yandexNdex}>ндекс</span>
</div>
<div style={s.searchBox}>
<input
style={s.searchInput}
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key === 'Enter' && setSearched(true)}
placeholder="Найти в интернете"
/>
<button style={s.searchBtn} onClick={() => setSearched(true)}>Найти</button>
</div>
</div>
)}
{/* Результаты поиска */}
{searched && !selectedSite && (
<div style={s.searchResults}>
<div style={s.searchHeader}>
<div style={s.searchBoxSmall}>
<span style={s.yandexSmall}>Я</span>
<input
style={s.searchInputSmall}
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key === 'Enter' && setSearched(true)}
/>
<button style={s.searchBtnSmall} onClick={() => setSearched(true)}>🔍</button>
</div>
<div style={s.resultCount}>Нашлось 10 результатов</div>
</div>
<div style={s.resultsList}>
{SITES.map(site => (
<div key={site.id} style={s.resultItem} onClick={() => openSite(site)}>
{site.adLabel && <span style={s.adBadge}>Реклама</span>}
<div style={s.resultName}>{site.name}</div>
<div style={{ ...s.resultDomain, color: site.https ? '#4a9e6e' : '#f5a623' }}>
{site.https ? '🔒' : '⚠'} {site.domain}
</div>
<div style={s.resultDesc}>{site.description}</div>
</div>
))}
</div>
</div>
)}
{/* Страница сайта */}
{selectedSite && (
<div style={s.sitePage}>
<div style={s.siteHeader}>
<span style={{ fontSize: 32 }}>📱</span>
<div> <div>
<MainApp showGosuslugi={false} showAmnezia={false}/> <div style={s.siteTitle}>ТелеЛитр</div>
<div style={s.siteDomain}>{selectedSite.domain}</div>
</div>
{!selectedSite.https && (
<span style={s.unsafeBadge}> HTTP небезопасный сайт</span>
)}
</div>
<div style={s.downloadCard}>
<div style={s.appInfo}>
<span style={{ fontSize: 56 }}>📱</span>
<div style={{ flex: 1 }}>
<div style={s.appName}>TeleLitr Messenger</div>
<div style={s.appVersion}>Версия {selectedSite.version}</div>
<div style={{ fontSize: 13, marginBottom: 4 }}>
<span style={{ color: '#666' }}>Файл: </span>
<span style={{ color: selectedSite.isLegit ? '#00FF41' : '#f5a623' }}>
{selectedSite.fileName}
</span>
</div>
<div style={{ color: '#555', fontSize: 12 }}>Размер: {selectedSite.fileSize}</div>
</div>
</div>
{!selectedSite.isLegit && (
<div style={s.warnHints}>
<div style={s.warnTitle}> Признаки подозрительного сайта:</div>
<ul style={s.hintList}>
{!selectedSite.https && <li>Сайт работает по HTTP данные не шифруются</li>}
{(selectedSite.domain.includes('.xyz') || selectedSite.domain.includes('.net') || selectedSite.domain.includes('.com') || selectedSite.domain.includes('mirror') || selectedSite.domain.includes('download')) && (
<li>Домен не совпадает с официальным <b>telelitr.ru</b></li>
)}
{selectedSite.domain !== 'telelitr.ru' && (
<li>Имя файла отличается от официального: <b>TeleLitr_Setup_2.1.4.exe</b></li>
)}
{selectedSite.adLabel && <li>Рекламная ссылка в поиске не гарантирует официальность</li>}
</ul>
</div>
)}
{selectedSite.isLegit && (
<div style={s.safeHints}>
<div style={s.safeTitle}> Признаки официального сайта:</div>
<ul style={s.hintList}>
<li>HTTPS соединение зашифровано</li>
<li>Домен <b>telelitr.ru</b> совпадает с официальным</li>
<li>Имя файла содержит точную версию: TeleLitr_Setup_<b>2.1.4</b>.exe</li>
<li>Цифровая подпись файла подтверждена</li>
</ul>
</div>
)}
<button
style={downloading ? s.dlBtnLoading : selectedSite.isLegit ? s.dlBtnSafe : s.dlBtnDanger}
onClick={handleDownload}
disabled={downloading}
>
{downloading ? '⟳ Загрузка...' : `↓ Скачать ${selectedSite.fileName}`}
</button>
{downloading && (
<div style={s.progressBar}>
<div style={s.progressFill} />
</div>
)}
</div>
</div>
)}
</div>
</div>
{/* Модал: вредонос */}
{modal === 'malware' && selectedSite && (
<div style={s.modalOverlay}>
<div style={s.modalDanger}>
<div style={s.modalIcon}></div>
<div style={s.modalTitle}>Вредоносное ПО обнаружено!</div>
<div style={s.modalText}>
Файл <b style={{ color: '#FF0040' }}>{selectedSite.fileName}</b> с сайта{' '}
<b style={{ color: '#FF0040' }}>{selectedSite.domain}</b> является вредоносным.
</div>
<div style={s.malwareBlock}>
<div style={{ color: '#aaa', fontSize: 13, marginBottom: 6 }}>
Тип угрозы: <b style={{ color: '#FF0040' }}>{selectedSite.malwareType}</b>
</div>
<div style={{ color: '#aaa', fontSize: 13, lineHeight: 1.6 }}>{selectedSite.malwareDesc}</div>
</div>
<div style={s.rulesBlock}>
<div style={s.rulesTitle}>Как найти официальный сайт:</div>
<ul style={s.rulesList}>
<li>Проверяйте домен он должен совпадать с официальным</li>
<li>HTTPS важен, но сам по себе не гарантирует безопасность</li>
<li>Имя файла должно содержать точную версию программы</li>
<li>Берите ссылку с сайта разработчика или из документации к ПО</li>
<li>Проверяйте цифровую подпись скачанного .exe файла</li>
</ul>
</div>
<button style={s.retryBtn} onClick={reset}> Вернуться к поиску</button>
</div>
</div>
)}
{/* Модал: успех */}
{modal === 'success' && selectedSite && (
<div style={s.modalOverlay}>
<div style={s.modalSuccess}>
<div style={s.modalIcon}></div>
<div style={{ ...s.modalTitle, color: '#00FF41' }}>Официальный установщик загружен!</div>
<div style={s.modalText}>
Вы скачали <b style={{ color: '#00FF41' }}>{selectedSite.fileName}</b> с официального сайта{' '}
<b style={{ color: '#00FF41' }}>{selectedSite.domain}</b>.
</div>
<div style={s.successDetails}>
<div style={s.successRow}><span>Файл:</span><span style={{ color: '#00FF41' }}>{selectedSite.fileName}</span></div>
<div style={s.successRow}><span>Версия:</span><span style={{ color: '#00FF41' }}>{selectedSite.version}</span></div>
<div style={s.successRow}><span>Подпись:</span><span style={{ color: '#00FF41' }}> Подтверждена</span></div>
<div style={s.successRow}><span>Угрозы:</span><span style={{ color: '#00FF41' }}>Не обнаружены</span></div>
</div>
<button style={s.completeBtn} onClick={() => { setModal(null); onComplete('win10'); }}>
Завершить кейс
</button>
</div>
</div>
)}
</div> </div>
); );
}; };
const s: Record<string, React.CSSProperties> = {
root: {
minHeight: '100vh',
background: 'linear-gradient(135deg, #0a0a14 0%, #0d1117 60%, #001400 100%)',
color: '#ccc',
fontFamily: "'Share Tech Mono', monospace",
padding: '0 0 40px',
},
story: {
maxWidth: 760, margin: '24px auto 0', padding: '14px 24px',
background: 'rgba(0,255,255,0.04)', border: '1px solid rgba(0,255,255,0.15)',
borderRadius: 8, fontSize: 14, lineHeight: 1.7, color: '#aaa', textAlign: 'center',
},
browser: {
maxWidth: 860, margin: '24px auto 0',
border: '1px solid rgba(0,255,255,0.2)', borderRadius: 10, overflow: 'hidden',
boxShadow: '0 0 30px rgba(0,255,255,0.06)',
},
chrome: {
background: '#080818', borderBottom: '1px solid rgba(0,255,255,0.15)',
padding: '8px 12px', display: 'flex', alignItems: 'center', gap: 8,
},
navBtn: {
background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.1)',
color: '#888', borderRadius: 4, padding: '4px 10px', cursor: 'pointer',
fontSize: 14, fontFamily: "'Share Tech Mono', monospace",
},
urlBar: {
flex: 1, background: '#0a0a1a', border: '1px solid rgba(0,255,255,0.2)',
borderRadius: 4, padding: '5px 12px', fontSize: 13,
display: 'flex', alignItems: 'center', gap: 8,
},
httpsIcon: { color: '#00FF41', fontSize: 13 },
httpIcon: { color: '#f5a623', fontSize: 13 },
urlText: { flex: 1 },
httpWarn: { color: '#f5a623', fontSize: 11 },
content: { background: '#0d0d0d', minHeight: 480 },
yandexHome: {
display: 'flex', flexDirection: 'column', alignItems: 'center',
justifyContent: 'center', paddingTop: 80, paddingBottom: 60, gap: 28,
},
yandexLogo: { fontSize: 52, fontWeight: 700, lineHeight: 1 },
yandexY: { color: '#FF0040', fontFamily: 'Georgia, serif' },
yandexNdex: { color: '#eee', fontFamily: 'Georgia, serif' },
searchBox: { display: 'flex', width: '100%', maxWidth: 560 },
searchInput: {
flex: 1, padding: '12px 16px', background: '#1a1a2e',
border: '1px solid rgba(0,255,255,0.25)', borderRight: 'none',
borderRadius: '6px 0 0 6px', color: '#eee', fontSize: 15,
fontFamily: "'Share Tech Mono', monospace", outline: 'none',
},
searchBtn: {
padding: '12px 24px', background: '#FF0040', border: 'none',
borderRadius: '0 6px 6px 0', color: '#fff',
fontFamily: "'Orbitron', monospace", fontSize: 13, fontWeight: 700, cursor: 'pointer',
},
searchResults: { padding: '0 0 24px' },
searchHeader: {
background: '#080818', borderBottom: '1px solid rgba(0,255,255,0.1)',
padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' as const,
},
searchBoxSmall: {
display: 'flex', alignItems: 'center', flex: 1, maxWidth: 400,
background: '#0d0d22', border: '1px solid rgba(0,255,255,0.2)',
borderRadius: 6, overflow: 'hidden',
},
yandexSmall: { color: '#FF0040', fontFamily: 'Georgia, serif', fontSize: 18, fontWeight: 700, padding: '0 8px' },
searchInputSmall: {
flex: 1, background: 'transparent', border: 'none', color: '#eee',
fontSize: 13, fontFamily: "'Share Tech Mono', monospace", padding: '8px 4px', outline: 'none',
},
searchBtnSmall: { background: 'transparent', border: 'none', color: '#888', cursor: 'pointer', padding: '8px 12px', fontSize: 14 },
resultCount: { color: '#555', fontSize: 12 },
resultsList: { padding: '8px 20px', display: 'flex', flexDirection: 'column', gap: 2 },
resultItem: { padding: '14px 16px', borderRadius: 6, cursor: 'pointer', borderBottom: '1px solid rgba(255,255,255,0.04)' },
adBadge: {
display: 'inline-block', background: 'rgba(245,166,35,0.15)',
border: '1px solid rgba(245,166,35,0.4)', color: '#f5a623',
fontSize: 10, padding: '1px 6px', borderRadius: 3, marginBottom: 4,
fontFamily: "'Share Tech Mono', monospace",
},
resultName: { color: '#5b9bd5', fontSize: 15, marginBottom: 2 },
resultDomain: { fontSize: 12, marginBottom: 4 },
resultDesc: { color: '#777', fontSize: 13, lineHeight: 1.5 },
sitePage: { padding: 24 },
siteHeader: {
display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20,
paddingBottom: 12, borderBottom: '1px solid rgba(255,255,255,0.06)',
},
siteTitle: { color: '#00FFFF', fontFamily: "'Orbitron', monospace", fontSize: 16, fontWeight: 700 },
siteDomain: { color: '#555', fontSize: 12, marginTop: 2 },
unsafeBadge: {
marginLeft: 'auto', background: 'rgba(245,166,35,0.12)',
border: '1px solid #f5a623', color: '#f5a623', fontSize: 11, padding: '4px 12px', borderRadius: 4,
},
downloadCard: {
background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 10, padding: 20,
},
appInfo: { display: 'flex', gap: 20, alignItems: 'flex-start', marginBottom: 20 },
appName: { color: '#ddd', fontSize: 18, fontWeight: 700, marginBottom: 4 },
appVersion: { color: '#888', fontSize: 13, marginBottom: 6 },
warnHints: {
background: 'rgba(245,166,35,0.06)', border: '1px solid rgba(245,166,35,0.3)',
borderRadius: 8, padding: '12px 16px', marginBottom: 16,
},
warnTitle: { color: '#f5a623', fontFamily: "'Orbitron', monospace", fontSize: 12, fontWeight: 700, marginBottom: 8 },
safeHints: {
background: 'rgba(0,255,65,0.05)', border: '1px solid rgba(0,255,65,0.25)',
borderRadius: 8, padding: '12px 16px', marginBottom: 16,
},
safeTitle: { color: '#00FF41', fontFamily: "'Orbitron', monospace", fontSize: 12, fontWeight: 700, marginBottom: 8 },
hintList: { margin: 0, paddingLeft: 20, color: '#aaa', fontSize: 13, lineHeight: 1.8 },
dlBtnSafe: {
width: '100%', padding: '13px', background: 'rgba(0,255,65,0.1)',
border: '1px solid #00FF41', color: '#00FF41', borderRadius: 7, cursor: 'pointer',
fontFamily: "'Orbitron', monospace", fontSize: 13, fontWeight: 700,
},
dlBtnDanger: {
width: '100%', padding: '13px', background: 'rgba(245,166,35,0.08)',
border: '1px solid #f5a623', color: '#f5a623', borderRadius: 7, cursor: 'pointer',
fontFamily: "'Orbitron', monospace", fontSize: 13, fontWeight: 700,
},
dlBtnLoading: {
width: '100%', padding: '13px', background: 'rgba(0,255,255,0.06)',
border: '1px solid rgba(0,255,255,0.3)', color: '#00FFFF', borderRadius: 7, cursor: 'not-allowed',
fontFamily: "'Orbitron', monospace", fontSize: 13, fontWeight: 700,
},
progressBar: { marginTop: 10, height: 3, background: 'rgba(0,255,255,0.1)', borderRadius: 2, overflow: 'hidden' },
progressFill: {
height: '100%', background: '#00FFFF', borderRadius: 2, width: '100%',
animation: 'none', transition: 'none',
boxShadow: '0 0 8px rgba(0,255,255,0.5)',
},
modalOverlay: {
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.82)',
display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
zIndex: 200, overflowY: 'auto', padding: '40px 16px',
},
modalDanger: {
background: '#0d0d22', border: '1px solid #FF0040', borderRadius: 12,
padding: '28px 28px 24px', maxWidth: 520, width: '100%',
boxShadow: '0 0 40px rgba(255,0,64,0.25)', textAlign: 'center',
},
modalSuccess: {
background: '#0d0d22', border: '1px solid #00FF41', borderRadius: 12,
padding: '28px 28px 24px', maxWidth: 480, width: '100%',
boxShadow: '0 0 40px rgba(0,255,65,0.2)', textAlign: 'center',
},
modalIcon: { fontSize: 48, marginBottom: 12 },
modalTitle: { color: '#FF0040', fontFamily: "'Orbitron', monospace", fontSize: 16, fontWeight: 700, marginBottom: 12 },
modalText: { color: '#aaa', fontSize: 13, lineHeight: 1.7, marginBottom: 16 },
malwareBlock: {
background: 'rgba(255,0,64,0.07)', border: '1px solid rgba(255,0,64,0.3)',
borderRadius: 8, padding: '12px 16px', marginBottom: 14, textAlign: 'left',
},
rulesBlock: {
background: 'rgba(0,255,65,0.04)', border: '1px solid rgba(0,255,65,0.2)',
borderRadius: 8, padding: '12px 16px', marginBottom: 20, textAlign: 'left',
},
rulesTitle: { color: '#00FF41', fontFamily: "'Orbitron', monospace", fontSize: 12, fontWeight: 700, marginBottom: 8 },
rulesList: { margin: 0, paddingLeft: 20, color: '#aaa', fontSize: 13, lineHeight: 1.8 },
retryBtn: {
padding: '10px 20px', background: 'transparent',
border: '1px solid rgba(0,255,255,0.3)', borderRadius: 6,
color: '#888', cursor: 'pointer', fontFamily: "'Share Tech Mono', monospace", fontSize: 13,
},
successDetails: {
background: 'rgba(0,255,65,0.05)', border: '1px solid rgba(0,255,65,0.2)',
borderRadius: 8, padding: '12px 16px', marginBottom: 20, textAlign: 'left',
},
successRow: { display: 'flex', justifyContent: 'space-between', padding: '4px 0', fontSize: 13, color: '#aaa' },
completeBtn: {
padding: '12px 36px', background: '#00FF41', border: 'none',
borderRadius: 6, color: '#000', fontFamily: "'Orbitron', monospace",
fontSize: 14, fontWeight: 700, cursor: 'pointer',
},
};
export default Case2Desktop; export default Case2Desktop;