fix colors, add new case
This commit is contained in:
parent
bbd8620e94
commit
042f282348
8 changed files with 1880 additions and 163 deletions
|
|
@ -4,13 +4,14 @@ import Case1Desktop from './cases/Case1Desktop';
|
|||
import Case2Desktop from './cases/Case2Desktop';
|
||||
import Case3Desktop from './cases/Case3Desktop';
|
||||
import Case4Desktop from './cases/Case4Desktop';
|
||||
import Case5Desktop from './cases/Case5Desktop';
|
||||
import ChatterboxTTS from './components/deepfake/ChatterboxTTS';
|
||||
import MyQuize from './components/MyQuize';
|
||||
import CyberSecurityArticle from './components/CyberSecurityArticle';
|
||||
|
||||
export type WallpaperType = 'xp' | 'win7' | 'win10';
|
||||
|
||||
type OverlayType = 'case1' | 'case2' | 'case3' | 'case4' | 'deepfake' | 'quiz' | 'wiki' | null;
|
||||
type OverlayType = 'case1' | 'case2' | 'case3' | 'case4' | 'case5' | 'deepfake' | 'quiz' | 'wiki' | null;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [overlay, setOverlay] = useState<OverlayType>(null);
|
||||
|
|
@ -23,6 +24,7 @@ const App: React.FC = () => {
|
|||
2: 'case2',
|
||||
3: 'case3',
|
||||
4: 'case4',
|
||||
5: 'case5',
|
||||
'deepfake': 'deepfake',
|
||||
};
|
||||
setTimeout(() => setOverlay(map[level] ?? null), 1200);
|
||||
|
|
@ -56,6 +58,7 @@ const App: React.FC = () => {
|
|||
{overlay === 'case2' && <Case2Desktop onComplete={close} />}
|
||||
{overlay === 'case3' && <Case3Desktop onComplete={close} />}
|
||||
{overlay === 'case4' && <Case4Desktop onComplete={close} />}
|
||||
{overlay === 'case5' && <Case5Desktop onComplete={close} />}
|
||||
{overlay === 'deepfake' && <ChatterboxTTS />}
|
||||
{overlay === 'quiz' && <MyQuize />}
|
||||
{overlay === 'wiki' && <CyberSecurityArticle />}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,456 @@
|
|||
import React, { useState } from 'react';
|
||||
import type { WallpaperType } from '../App';
|
||||
import MainApp from '../MainApp';
|
||||
// import './Case3Desktop.css';
|
||||
|
||||
interface Case3DesktopProps {
|
||||
onComplete: (type: WallpaperType) => void;
|
||||
}
|
||||
|
||||
type Page =
|
||||
| 'home'
|
||||
| 'mail-inbox'
|
||||
| 'mail-real'
|
||||
| 'mail-phishing'
|
||||
| 'fake-gosuslugi'
|
||||
| 'gosuslugi';
|
||||
|
||||
const PAGE_URL: Record<Page, { url: string; secure: boolean }> = {
|
||||
'home': { url: 'yandex.ru', secure: true },
|
||||
'mail-inbox': { url: 'mail.yandex.ru', secure: true },
|
||||
'mail-real': { url: 'mail.yandex.ru', secure: true },
|
||||
'mail-phishing': { url: 'mail.yandex.ru', secure: true },
|
||||
'fake-gosuslugi': { url: 'g0suslugi-confirm.ru/login', secure: false },
|
||||
'gosuslugi': { url: 'gosuslugi.ru', secure: true },
|
||||
};
|
||||
|
||||
const Case3Desktop: React.FC<Case3DesktopProps> = ({ onComplete }) => {
|
||||
const [page, setPage] = useState<Page>('home');
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [modal, setModal] = useState<'stolen' | 'success' | null>(null);
|
||||
|
||||
const { url, secure } = PAGE_URL[page];
|
||||
|
||||
const goTo = (p: Page) => { setPage(p); setLogin(''); setPassword(''); };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MainApp showGosuslugi={true} showAmnezia={false}/>
|
||||
<div style={s.root}>
|
||||
{/* Браузер */}
|
||||
<div style={s.browser}>
|
||||
{/* Хром */}
|
||||
<div style={s.chrome}>
|
||||
<button style={s.navBtn} onClick={() => goTo('home')}>⌂</button>
|
||||
<button style={s.navBtn} onClick={() => {
|
||||
if (page === 'fake-gosuslugi') goTo('mail-phishing');
|
||||
else if (page === 'mail-real' || page === 'mail-phishing') goTo('mail-inbox');
|
||||
else if (page === 'mail-inbox' || page === 'gosuslugi') goTo('home');
|
||||
}}>←</button>
|
||||
<div style={s.urlBar}>
|
||||
<span style={secure ? s.httpsIcon : s.httpIcon}>{secure ? '🔒' : '⚠'}</span>
|
||||
<span style={{ ...s.urlText, color: secure ? '#aaa' : '#f5a623' }}>{url}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Закладки */}
|
||||
<div style={s.bookmarks}>
|
||||
<button style={s.bookmark} onClick={() => goTo('mail-inbox')}>📧 Почта</button>
|
||||
<button style={s.bookmark} onClick={() => goTo('gosuslugi')}>🏛 Госуслуги</button>
|
||||
</div>
|
||||
|
||||
{/* Контент */}
|
||||
<div style={s.content}>
|
||||
{page === 'home' && <HomePage onGo={goTo} />}
|
||||
{page === 'mail-inbox' && <MailInbox onOpen={goTo} />}
|
||||
{page === 'mail-real' && <EmailReal onBack={() => goTo('mail-inbox')} onGosuslugi={() => goTo('gosuslugi')} />}
|
||||
{page === 'mail-phishing' && <EmailPhishing onBack={() => goTo('mail-inbox')} onLink={() => goTo('fake-gosuslugi')} />}
|
||||
{page === 'fake-gosuslugi' && (
|
||||
<FakeGosuslugi
|
||||
login={login} password={password}
|
||||
setLogin={setLogin} setPassword={setPassword}
|
||||
onSubmit={() => setModal('stolen')}
|
||||
/>
|
||||
)}
|
||||
{page === 'gosuslugi' && (
|
||||
<RealGosuslugi
|
||||
login={login} password={password}
|
||||
setLogin={setLogin} setPassword={setPassword}
|
||||
onSubmit={() => setModal('success')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Модалки */}
|
||||
{modal === 'stolen' && (
|
||||
<div style={s.modalOverlay}>
|
||||
<div style={{ ...s.modal, borderColor: '#FF0040' }}>
|
||||
<div style={s.modalIcon}>💀</div>
|
||||
<div style={{ ...s.modalTitle, color: '#FF0040' }}>Аккаунт украден!</div>
|
||||
<div style={s.modalText}>
|
||||
Вы ввели логин и пароль на <b style={{ color: '#FF0040' }}>фишинговом сайте</b>.<br/>
|
||||
Домен <b>g0suslugi-confirm.ru</b> — подделка под госуслуги.<br/>
|
||||
Буква <b>o</b> заменена на цифру <b>0</b>.<br/><br/>
|
||||
Злоумышленник получил ваши данные и вошёл в ваш аккаунт.
|
||||
</div>
|
||||
<div style={s.modalHint}>
|
||||
💡 Правило: никогда не переходите по ссылкам из писем. Открывайте госуслуги только из закладок.
|
||||
</div>
|
||||
<button style={{ ...s.modalBtn, background: 'rgba(255,0,64,0.15)', border: '1px solid #FF0040', color: '#FF0040' }}
|
||||
onClick={() => { setModal(null); goTo('mail-phishing'); }}>
|
||||
Попробовать ещё раз
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{modal === 'success' && (
|
||||
<div style={s.modalOverlay}>
|
||||
<div style={{ ...s.modal, borderColor: '#00FF41' }}>
|
||||
<div style={s.modalIcon}>🛡️</div>
|
||||
<div style={{ ...s.modalTitle, color: '#00FF41' }}>Всё верно!</div>
|
||||
<div style={s.modalText}>
|
||||
Вы открыли <b style={{ color: '#00FF41' }}>официальный сайт</b> госуслуг напрямую из закладок.<br/>
|
||||
Домен <b>gosuslugi.ru</b> — настоящий, соединение защищено HTTPS.<br/><br/>
|
||||
Ваши данные в безопасности.
|
||||
</div>
|
||||
<button style={{ ...s.modalBtn, background: '#00FF41', border: 'none', color: '#000' }}
|
||||
onClick={() => { setModal(null); onComplete('win10'); }}>
|
||||
Завершить кейс ✓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Страницы ─────────────────────────────────────────
|
||||
|
||||
const HomePage: React.FC<{ onGo: (p: Page) => void }> = ({ onGo }) => (
|
||||
<div style={s.homePage}>
|
||||
<div style={s.yandexLogo}>Яндекс</div>
|
||||
<div style={s.homeHint}>Используйте закладки выше или откройте почту для проверки письма</div>
|
||||
<div style={s.homeIcons}>
|
||||
<button style={s.homeIcon} onClick={() => onGo('mail-inbox')}>
|
||||
<span style={{ fontSize: 32 }}>📧</span>
|
||||
<span>Почта</span>
|
||||
</button>
|
||||
<button style={s.homeIcon} onClick={() => onGo('gosuslugi')}>
|
||||
<span style={{ fontSize: 32 }}>🏛</span>
|
||||
<span>Госуслуги</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const MailInbox: React.FC<{ onOpen: (p: Page) => void }> = ({ onOpen }) => (
|
||||
<div style={s.mailPage}>
|
||||
<div style={s.mailHeader}>Входящие <span style={{ color: '#00FFFF', fontSize: 12 }}>2 новых</span></div>
|
||||
{/* Фишинговое письмо — выглядит срочно, идёт первым */}
|
||||
<div style={{ ...s.emailRow, borderLeft: '3px solid #f5a623' }} onClick={() => onOpen('mail-phishing')}>
|
||||
<div style={s.emailFrom}>
|
||||
<span style={s.emailSender}>security@g0suslugi-confirm.ru</span>
|
||||
<span style={s.emailTime}>10:47</span>
|
||||
</div>
|
||||
<div style={s.emailSubject}>⚠ Требуется подтверждение аккаунта</div>
|
||||
<div style={s.emailPreview}>Ваш аккаунт будет заблокирован. Подтвердите данные в течение 24 часов...</div>
|
||||
</div>
|
||||
{/* Настоящее письмо */}
|
||||
<div style={{ ...s.emailRow, borderLeft: '3px solid rgba(0,255,255,0.3)' }} onClick={() => onOpen('mail-real')}>
|
||||
<div style={s.emailFrom}>
|
||||
<span style={s.emailSender}>noreply@gosuslugi.ru</span>
|
||||
<span style={s.emailTime}>09:12</span>
|
||||
</div>
|
||||
<div style={s.emailSubject}>Ваша заявка №2847 отклонена</div>
|
||||
<div style={s.emailPreview}>Заявка на получение справки была отклонена в связи с неполным пакетом...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const EmailReal: React.FC<{ onBack: () => void; onGosuslugi: () => void }> = ({ onBack, onGosuslugi }) => (
|
||||
<div style={s.emailPage}>
|
||||
<button style={s.backLink} onClick={onBack}>← Входящие</button>
|
||||
<div style={s.emailFullFrom}>
|
||||
<b>От:</b> <span style={{ color: '#00FF41' }}>noreply@gosuslugi.ru</span>
|
||||
</div>
|
||||
<div style={s.emailFullSubject}>Ваша заявка №2847 отклонена</div>
|
||||
<div style={s.emailBody}>
|
||||
<p>Уважаемый пользователь,</p>
|
||||
<p>Ваша заявка на получение справки об отсутствии задолженностей (<b>№2847</b>) была отклонена.</p>
|
||||
<p><b>Причина:</b> неполный пакет документов. Отсутствует копия паспорта.</p>
|
||||
<p>Для повторной подачи заявки войдите на портал Госуслуги.</p>
|
||||
<p style={{ color: '#888', fontSize: 12 }}>Это автоматическое сообщение, не отвечайте на него.</p>
|
||||
</div>
|
||||
<div style={s.emailActions}>
|
||||
<span style={{ color: '#888', fontSize: 13 }}>Нужно войти на Госуслуги для повторной подачи</span>
|
||||
<button style={s.actionBtn} onClick={onGosuslugi}>Открыть Госуслуги из закладок →</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const EmailPhishing: React.FC<{ onBack: () => void; onLink: () => void }> = ({ onBack, onLink }) => (
|
||||
<div style={s.emailPage}>
|
||||
<button style={s.backLink} onClick={onBack}>← Входящие</button>
|
||||
<div style={{ ...s.emailFullFrom, color: '#f5a623' }}>
|
||||
<b>От:</b> <span style={{ color: '#f5a623' }}>security@g0suslugi-confirm.ru</span>
|
||||
<span style={s.suspiciousBadge}>⚠ Подозрительный отправитель</span>
|
||||
</div>
|
||||
<div style={s.emailFullSubject}>⚠ Требуется срочное подтверждение аккаунта</div>
|
||||
<div style={s.emailBody}>
|
||||
<p>Уважаемый пользователь,</p>
|
||||
<p>Мы зафиксировали <b style={{ color: '#FF0040' }}>подозрительную активность</b> в вашем аккаунте.</p>
|
||||
<p>В целях безопасности ваш аккаунт будет <b style={{ color: '#FF0040' }}>заблокирован через 24 часа</b>, если вы не подтвердите личность.</p>
|
||||
<p>Нажмите кнопку ниже для подтверждения:</p>
|
||||
<button style={s.phishingLink} onClick={onLink}>
|
||||
Подтвердить аккаунт →
|
||||
</button>
|
||||
<p style={{ color: '#555', fontSize: 11, marginTop: 20 }}>
|
||||
© Портал государственных услуг. Если вы не запрашивали это письмо — проигнорируйте его.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const FakeGosuslugi: React.FC<{
|
||||
login: string; password: string;
|
||||
setLogin: (v: string) => void; setPassword: (v: string) => void;
|
||||
onSubmit: () => void;
|
||||
}> = ({ login, password, setLogin, setPassword, onSubmit }) => (
|
||||
<div style={s.gosPage}>
|
||||
<div style={s.fakeWarning}>⚠ HTTP — незащищённое соединение</div>
|
||||
<div style={s.gosHeader}>
|
||||
<span style={{ fontSize: 28 }}>🏛</span>
|
||||
<span style={s.gosTitle}>Госуслуги</span>
|
||||
</div>
|
||||
<div style={s.gosSubtitle}>Подтверждение личности</div>
|
||||
<div style={s.loginForm}>
|
||||
<input style={s.input} placeholder="Логин или телефон" value={login} onChange={e => setLogin(e.target.value)} />
|
||||
<input style={s.input} type="password" placeholder="Пароль" value={password} onChange={e => setPassword(e.target.value)} />
|
||||
<button style={{ ...s.loginBtn, background: '#f5a623', color: '#000' }}
|
||||
onClick={() => { if (login && password) onSubmit(); }}
|
||||
disabled={!login || !password}>
|
||||
Подтвердить
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ color: '#555', fontSize: 11, textAlign: 'center', marginTop: 12 }}>
|
||||
g0suslugi-confirm.ru
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const RealGosuslugi: React.FC<{
|
||||
login: string; password: string;
|
||||
setLogin: (v: string) => void; setPassword: (v: string) => void;
|
||||
onSubmit: () => void;
|
||||
}> = ({ login, password, setLogin, setPassword, onSubmit }) => (
|
||||
<div style={s.gosPage}>
|
||||
<div style={s.realSafe}>🔒 Безопасное соединение · gosuslugi.ru</div>
|
||||
<div style={s.gosHeader}>
|
||||
<span style={{ fontSize: 28 }}>🏛</span>
|
||||
<span style={s.gosTitle}>Госуслуги</span>
|
||||
</div>
|
||||
<div style={s.gosSubtitle}>Войдите в личный кабинет</div>
|
||||
<div style={s.loginForm}>
|
||||
<input style={s.input} placeholder="Логин или телефон" value={login} onChange={e => setLogin(e.target.value)} />
|
||||
<input style={s.input} type="password" placeholder="Пароль" value={password} onChange={e => setPassword(e.target.value)} />
|
||||
<button style={{ ...s.loginBtn, background: '#00FFFF', color: '#000' }}
|
||||
onClick={() => { if (login && password) onSubmit(); }}
|
||||
disabled={!login || !password}>
|
||||
Войти
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ color: '#555', fontSize: 11, textAlign: 'center', marginTop: 12 }}>
|
||||
gosuslugi.ru · Официальный портал
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// ── Стили ─────────────────────────────────────────────
|
||||
|
||||
const s: Record<string, React.CSSProperties> = {
|
||||
root: {
|
||||
minHeight: '100vh',
|
||||
background: '#0a0a0a',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '24px 16px',
|
||||
fontFamily: "'Share Tech Mono', monospace",
|
||||
},
|
||||
browser: {
|
||||
width: '100%',
|
||||
maxWidth: 800,
|
||||
background: '#0d0d22',
|
||||
border: '1px solid rgba(0,255,255,0.2)',
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 0 40px rgba(0,255,255,0.08)',
|
||||
},
|
||||
chrome: {
|
||||
background: '#080818',
|
||||
borderBottom: '1px solid rgba(0,255,255,0.15)',
|
||||
padding: '10px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
navBtn: {
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
color: '#888',
|
||||
borderRadius: 4,
|
||||
padding: '4px 8px',
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
},
|
||||
urlBar: {
|
||||
flex: 1,
|
||||
background: '#0a0a1a',
|
||||
border: '1px solid rgba(0,255,255,0.15)',
|
||||
borderRadius: 4,
|
||||
padding: '6px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
fontSize: 13,
|
||||
},
|
||||
httpsIcon: { color: '#00FF41', fontSize: 14 },
|
||||
httpIcon: { color: '#f5a623', fontSize: 14 },
|
||||
urlText: { flex: 1 },
|
||||
bookmarks: {
|
||||
background: '#060612',
|
||||
borderBottom: '1px solid rgba(0,255,255,0.08)',
|
||||
padding: '6px 12px',
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
},
|
||||
bookmark: {
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(0,255,255,0.15)',
|
||||
color: '#888',
|
||||
borderRadius: 4,
|
||||
padding: '3px 10px',
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
fontFamily: "'Share Tech Mono', monospace",
|
||||
},
|
||||
content: { minHeight: 420 },
|
||||
|
||||
// Home
|
||||
homePage: {
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
||||
justifyContent: 'center', padding: 40, gap: 20,
|
||||
},
|
||||
yandexLogo: {
|
||||
fontSize: 48, fontWeight: 900, color: '#FF0040',
|
||||
fontFamily: "'Orbitron', monospace", letterSpacing: 2,
|
||||
},
|
||||
homeHint: { color: '#555', fontSize: 13, textAlign: 'center' },
|
||||
homeIcons: { display: 'flex', gap: 32, marginTop: 16 },
|
||||
homeIcon: {
|
||||
background: 'rgba(0,255,255,0.04)', border: '1px solid rgba(0,255,255,0.15)',
|
||||
borderRadius: 10, padding: '16px 24px', cursor: 'pointer',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
||||
gap: 8, color: '#aaa', fontSize: 13, fontFamily: "'Share Tech Mono', monospace",
|
||||
},
|
||||
|
||||
// Mail
|
||||
mailPage: { padding: 0 },
|
||||
mailHeader: {
|
||||
padding: '14px 20px', borderBottom: '1px solid rgba(0,255,255,0.1)',
|
||||
color: '#ccc', fontFamily: "'Orbitron', monospace", fontSize: 14,
|
||||
display: 'flex', alignItems: 'center', gap: 10,
|
||||
},
|
||||
emailRow: {
|
||||
padding: '14px 20px', borderBottom: '1px solid rgba(255,255,255,0.05)',
|
||||
cursor: 'pointer', transition: 'background 0.15s',
|
||||
},
|
||||
emailFrom: { display: 'flex', justifyContent: 'space-between', marginBottom: 4 },
|
||||
emailSender: { color: '#aaa', fontSize: 13, fontWeight: 'bold' },
|
||||
emailTime: { color: '#555', fontSize: 12 },
|
||||
emailSubject: { color: '#ddd', fontSize: 14, marginBottom: 3 },
|
||||
emailPreview: { color: '#555', fontSize: 12 },
|
||||
|
||||
// Email view
|
||||
emailPage: { padding: 20 },
|
||||
backLink: {
|
||||
background: 'transparent', border: 'none', color: '#00FFFF',
|
||||
cursor: 'pointer', fontSize: 13, padding: 0, marginBottom: 16,
|
||||
fontFamily: "'Share Tech Mono', monospace",
|
||||
},
|
||||
emailFullFrom: { fontSize: 13, color: '#888', marginBottom: 6, display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' },
|
||||
emailFullSubject: { color: '#ddd', fontSize: 16, fontWeight: 'bold', marginBottom: 16, fontFamily: "'Orbitron', monospace" },
|
||||
emailBody: { color: '#aaa', fontSize: 14, lineHeight: 1.8, marginBottom: 20 },
|
||||
suspiciousBadge: {
|
||||
background: 'rgba(245,166,35,0.12)', border: '1px solid #f5a623',
|
||||
color: '#f5a623', fontSize: 11, borderRadius: 4, padding: '2px 8px',
|
||||
},
|
||||
phishingLink: {
|
||||
background: 'rgba(0,120,255,0.15)', border: '1px solid #0078ff',
|
||||
color: '#4da6ff', borderRadius: 6, padding: '10px 20px',
|
||||
cursor: 'pointer', fontSize: 14, fontFamily: "'Share Tech Mono', monospace",
|
||||
display: 'block', marginTop: 8,
|
||||
},
|
||||
emailActions: {
|
||||
borderTop: '1px solid rgba(0,255,255,0.1)',
|
||||
paddingTop: 16, display: 'flex', flexDirection: 'column', gap: 10,
|
||||
},
|
||||
actionBtn: {
|
||||
background: '#00FFFF', border: 'none', borderRadius: 6,
|
||||
padding: '10px 20px', color: '#000', cursor: 'pointer',
|
||||
fontSize: 13, fontFamily: "'Orbitron', monospace", fontWeight: 700,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
|
||||
// Gosuslugi pages
|
||||
gosPage: { padding: 32, display: 'flex', flexDirection: 'column', alignItems: 'center' },
|
||||
fakeWarning: {
|
||||
background: 'rgba(245,166,35,0.12)', border: '1px solid #f5a623',
|
||||
color: '#f5a623', borderRadius: 6, padding: '8px 16px',
|
||||
fontSize: 12, marginBottom: 24, width: '100%', textAlign: 'center',
|
||||
},
|
||||
realSafe: {
|
||||
background: 'rgba(0,255,65,0.08)', border: '1px solid #00FF41',
|
||||
color: '#00FF41', borderRadius: 6, padding: '8px 16px',
|
||||
fontSize: 12, marginBottom: 24, width: '100%', textAlign: 'center',
|
||||
},
|
||||
gosHeader: { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 6 },
|
||||
gosTitle: { color: '#00FFFF', fontFamily: "'Orbitron', monospace", fontSize: 22, fontWeight: 700 },
|
||||
gosSubtitle: { color: '#888', fontSize: 13, marginBottom: 24 },
|
||||
loginForm: { display: 'flex', flexDirection: 'column', gap: 12, width: '100%', maxWidth: 320 },
|
||||
input: {
|
||||
background: '#0a0a1a', border: '1px solid rgba(0,255,255,0.2)',
|
||||
borderRadius: 6, padding: '10px 14px', color: '#ccc',
|
||||
fontSize: 14, fontFamily: "'Share Tech Mono', monospace",
|
||||
outline: 'none',
|
||||
},
|
||||
loginBtn: {
|
||||
padding: '12px', borderRadius: 6, border: 'none',
|
||||
fontFamily: "'Orbitron', monospace", fontSize: 14, fontWeight: 700,
|
||||
cursor: 'pointer', transition: 'opacity 0.2s',
|
||||
},
|
||||
|
||||
// Modals
|
||||
modalOverlay: {
|
||||
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.8)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 200,
|
||||
},
|
||||
modal: {
|
||||
background: '#0d0d22', border: '2px solid',
|
||||
borderRadius: 12, padding: '32px 28px', maxWidth: 440, width: '90%',
|
||||
textAlign: 'center', boxShadow: '0 0 40px rgba(0,0,0,0.5)',
|
||||
},
|
||||
modalIcon: { fontSize: 48, marginBottom: 12 },
|
||||
modalTitle: { fontFamily: "'Orbitron', monospace", fontSize: 18, fontWeight: 700, marginBottom: 12 },
|
||||
modalText: { color: '#aaa', fontSize: 14, lineHeight: 1.7, marginBottom: 16 },
|
||||
modalHint: {
|
||||
background: 'rgba(0,255,255,0.06)', border: '1px solid rgba(0,255,255,0.2)',
|
||||
borderRadius: 6, padding: '10px 14px', color: '#888', fontSize: 13,
|
||||
marginBottom: 20, textAlign: 'left',
|
||||
},
|
||||
modalBtn: {
|
||||
padding: '12px 28px', borderRadius: 6, cursor: 'pointer',
|
||||
fontFamily: "'Orbitron', monospace", fontSize: 13, fontWeight: 700,
|
||||
},
|
||||
};
|
||||
|
||||
export default Case3Desktop;
|
||||
|
|
|
|||
1138
src/cases/Case5Desktop.tsx
Normal file
1138
src/cases/Case5Desktop.tsx
Normal file
|
|
@ -0,0 +1,1138 @@
|
|||
import React, { useState } from 'react';
|
||||
import type { WallpaperType } from '../App';
|
||||
|
||||
interface Case5DesktopProps {
|
||||
onComplete: (type: WallpaperType) => void;
|
||||
}
|
||||
|
||||
interface Site {
|
||||
id: number;
|
||||
name: string;
|
||||
domain: string;
|
||||
https: boolean;
|
||||
isLegit: boolean;
|
||||
description: string;
|
||||
price: number;
|
||||
productName: string;
|
||||
paymentProcessor: string | null; // null = прямая форма (скиммер)
|
||||
skimmerHint: string | null;
|
||||
adLabel?: boolean;
|
||||
}
|
||||
|
||||
const SITES: Site[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'KleyMaster — магазин клея',
|
||||
domain: 'kley-master.ru',
|
||||
https: true,
|
||||
isLegit: true,
|
||||
description: 'Широкий ассортимент клея: ПВА, момент, эпоксидный. Доставка по России.',
|
||||
price: 149,
|
||||
productName: 'Клей ПВА "Универсал" 250мл',
|
||||
paymentProcessor: 'Тинькофф',
|
||||
skimmerHint: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Kley-Online — купить клей дёшево',
|
||||
domain: 'kley-online.shop',
|
||||
https: false,
|
||||
isLegit: false,
|
||||
adLabel: true,
|
||||
description: 'Клей ПВА, суперклей, момент. Скидки до 70%! Доставка 1 день.',
|
||||
price: 99,
|
||||
productName: 'Клей ПВА 250мл',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'HTTP-соединение: данные карты передаются в открытом виде',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'SuperKley Discount — акции и скидки',
|
||||
domain: 'superkley-discount.ru',
|
||||
https: false,
|
||||
isLegit: false,
|
||||
adLabel: true,
|
||||
description: 'Суперклей, ПВА, эпоксидный. Оптовые цены. Акция: -50% на всё!',
|
||||
price: 75,
|
||||
productName: 'ПВА клей универсальный',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Домен зарегистрирован 3 дня назад, нет контактов и юридических данных',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'HobbyTrade — товары для творчества',
|
||||
domain: 'hobbytrade-shop.ru',
|
||||
https: true,
|
||||
isLegit: true,
|
||||
description: 'Материалы для рукоделия, клей, краски. Более 15 лет на рынке.',
|
||||
price: 162,
|
||||
productName: 'Клей ПВА "Универсал" 250мл',
|
||||
paymentProcessor: 'СберПей',
|
||||
skimmerHint: null,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'KleyCheap — дешевле не бывает',
|
||||
domain: 'kleycheap.xyz',
|
||||
https: false,
|
||||
isLegit: false,
|
||||
adLabel: true,
|
||||
description: 'Клей по ценам производителя. Без наценки! Только сегодня -60%.',
|
||||
price: 59,
|
||||
productName: 'Клей ПВА 250мл АКЦИЯ',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Домен в зоне .xyz — часто используется мошенниками',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Kley-Market Online — каталог клея',
|
||||
domain: 'kley-market-online.ru',
|
||||
https: true,
|
||||
isLegit: false,
|
||||
description: 'Большой каталог клея. Быстрая доставка. Безопасная оплата.',
|
||||
price: 139,
|
||||
productName: 'Клей ПВА 250мл',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Форма оплаты не перенаправляет на банк — данные карты попадают прямо на сайт',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'BestKley — выгодные покупки',
|
||||
domain: 'best-kley.site',
|
||||
https: false,
|
||||
isLegit: false,
|
||||
adLabel: true,
|
||||
description: 'Лучший клей по лучшим ценам. Гарантия качества. Отзывы покупателей.',
|
||||
price: 89,
|
||||
productName: 'Клей ПВА BEST 250мл',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Домен .site и отсутствие HTTPS — типичные признаки фишингового магазина',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Kley-Opt — оптовые цены',
|
||||
domain: 'kley-opt24.ru',
|
||||
https: true,
|
||||
isLegit: false,
|
||||
description: 'Клей оптом и в розницу. Цены от производителя. Доставка 2-3 дня.',
|
||||
price: 119,
|
||||
productName: 'Клей ПВА универсальный',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Скопированный дизайн чужого магазина, платёжная форма ведёт на сторонний сервер',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'MegaKley24 — акционные предложения',
|
||||
domain: 'megakley24.ru',
|
||||
https: true,
|
||||
isLegit: false,
|
||||
adLabel: true,
|
||||
description: 'Клей ПВА, момент, суперклей. 24/7. Карты Visa/MasterCard. Быстро.',
|
||||
price: 109,
|
||||
productName: 'Клей ПВА 250мл выгодно',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Страница оплаты загружает сторонний скрипт, перехватывающий данные карты',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Kley-Sale — распродажа клея',
|
||||
domain: 'kley-sale.shop',
|
||||
https: false,
|
||||
isLegit: false,
|
||||
description: 'Распродажа! Клей ПВА, эпоксидный, момент. Только сегодня!',
|
||||
price: 49,
|
||||
productName: 'Клей ПВА SALE',
|
||||
paymentProcessor: null,
|
||||
skimmerHint: 'Сайт создан вчера, нет ИНН/ОГРН, телефон не существует',
|
||||
},
|
||||
];
|
||||
|
||||
type PageType = 'search' | 'product' | 'payment' | null;
|
||||
|
||||
const Case5Desktop: React.FC<Case5DesktopProps> = ({ onComplete }) => {
|
||||
const [searchQuery, setSearchQuery] = useState('купить клей пва 250мл');
|
||||
const [searched, setSearched] = useState(false);
|
||||
const [selectedSite, setSelectedSite] = useState<Site | null>(null);
|
||||
const [page, setPage] = useState<PageType>(null);
|
||||
const [cardNum, setCardNum] = useState('');
|
||||
const [cardExp, setCardExp] = useState('');
|
||||
const [cardCvv, setCardCvv] = useState('');
|
||||
const [modal, setModal] = useState<'skimmer' | 'success' | null>(null);
|
||||
|
||||
const currentUrl = (() => {
|
||||
if (!selectedSite) return 'ya.ru';
|
||||
if (page === 'product') return `${selectedSite.domain}/catalog/kley-pva`;
|
||||
if (page === 'payment') return `${selectedSite.domain}/checkout/payment`;
|
||||
return 'ya.ru';
|
||||
})();
|
||||
|
||||
const isSecure = (() => {
|
||||
if (!selectedSite) return true;
|
||||
return selectedSite.https;
|
||||
})();
|
||||
|
||||
const goSearch = () => setSearched(true);
|
||||
const openSite = (site: Site) => { setSelectedSite(site); setPage('product'); };
|
||||
const goToPayment = () => setPage('payment');
|
||||
|
||||
const handlePay = () => {
|
||||
if (!selectedSite) return;
|
||||
if (selectedSite.isLegit) {
|
||||
setModal('success');
|
||||
} else {
|
||||
setModal('skimmer');
|
||||
}
|
||||
};
|
||||
|
||||
const resetToSearch = () => {
|
||||
setModal(null);
|
||||
setSelectedSite(null);
|
||||
setPage(null);
|
||||
setCardNum('');
|
||||
setCardExp('');
|
||||
setCardCvv('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={s.root}>
|
||||
{/* Легенда */}
|
||||
<div style={s.story}>
|
||||
<p>
|
||||
Вам нужно купить клей ПВА для ремонта. Вы открываете Яндекс и ищете товар в интернете.
|
||||
<br />
|
||||
<b style={{ color: '#f5a623' }}>Задача:</b> найти надёжный магазин и безопасно оплатить покупку.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Браузер */}
|
||||
<div style={s.browser}>
|
||||
{/* Хром браузера */}
|
||||
<div style={s.chrome}>
|
||||
{selectedSite && (
|
||||
<button style={s.navBtn} onClick={resetToSearch}>←</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.httpWarnText}>Небезопасно</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={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && goSearch()}
|
||||
placeholder="Найти в интернете"
|
||||
/>
|
||||
<button style={s.searchBtn} onClick={goSearch}>Найти</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* === РЕЗУЛЬТАТЫ ПОИСКА === */}
|
||||
{searched && !selectedSite && (
|
||||
<div style={s.searchResults}>
|
||||
<div style={s.searchResultsHeader}>
|
||||
<div style={s.searchBoxSmall}>
|
||||
<span style={s.yandexSmall}>Я</span>
|
||||
<input
|
||||
style={s.searchInputSmall}
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && goSearch()}
|
||||
/>
|
||||
<button style={s.searchBtnSmall} onClick={goSearch}>🔍</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 style={s.resultPrice}>от {site.price} ₽</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* === СТРАНИЦА ТОВАРА === */}
|
||||
{selectedSite && page === 'product' && (
|
||||
<div style={s.productPage}>
|
||||
<div style={s.shopHeader}>
|
||||
<span style={s.shopName}>{selectedSite.name}</span>
|
||||
{!selectedSite.https && (
|
||||
<span style={s.unsafeBadge}>⚠ HTTP — небезопасный сайт</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={s.productCard}>
|
||||
<div style={s.productImage}>
|
||||
<span style={{ fontSize: 64 }}>🧴</span>
|
||||
</div>
|
||||
<div style={s.productInfo}>
|
||||
<div style={s.productTitle}>{selectedSite.productName}</div>
|
||||
<div style={s.productPriceTag}>{selectedSite.price} ₽</div>
|
||||
<div style={s.productDesc}>
|
||||
Объём: 250 мл · В наличии · Доставка 2–5 дней
|
||||
</div>
|
||||
<button style={s.buyBtn} onClick={goToPayment}>
|
||||
🛒 Купить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Подозрительные признаки */}
|
||||
{!selectedSite.isLegit && (
|
||||
<div style={s.suspiciousHints}>
|
||||
<div style={s.hintTitle}>💡 Обратите внимание на признаки сайта:</div>
|
||||
<ul style={s.hintList}>
|
||||
{!selectedSite.https && <li>⚠ Сайт не использует HTTPS</li>}
|
||||
{selectedSite.domain.includes('.xyz') && <li>⚠ Нестандартный домен (.xyz)</li>}
|
||||
{selectedSite.domain.includes('.shop') && <li>⚠ Нестандартный домен (.shop)</li>}
|
||||
{selectedSite.domain.includes('.site') && <li>⚠ Нестандартный домен (.site)</li>}
|
||||
{selectedSite.adLabel && <li>ℹ Рекламная ссылка в поиске</li>}
|
||||
<li>ℹ Нет раздела «О компании», нет ИНН / ОГРН</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{selectedSite.isLegit && (
|
||||
<div style={s.legitHints}>
|
||||
<div style={s.hintTitleGood}>✅ Признаки надёжного магазина:</div>
|
||||
<ul style={s.hintList}>
|
||||
<li>✅ HTTPS — соединение зашифровано</li>
|
||||
<li>✅ Известный домен .ru</li>
|
||||
<li>✅ Есть раздел «О нас» с ИНН и ОГРН</li>
|
||||
<li>✅ Оплата через проверенную платёжную систему</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* === СТРАНИЦА ОПЛАТЫ === */}
|
||||
{selectedSite && page === 'payment' && (
|
||||
<div style={s.paymentPage}>
|
||||
<div style={s.paymentTitle}>Оформление заказа</div>
|
||||
|
||||
{/* Сводка заказа */}
|
||||
<div style={s.orderSummary}>
|
||||
<div style={s.orderRow}>
|
||||
<span style={s.orderLabel}>Товар:</span>
|
||||
<span style={s.orderValue}>{selectedSite.productName}</span>
|
||||
</div>
|
||||
<div style={s.orderRow}>
|
||||
<span style={s.orderLabel}>Сумма:</span>
|
||||
<span style={{ ...s.orderValue, color: '#00FF41', fontWeight: 700 }}>
|
||||
{selectedSite.price} ₽
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Форма оплаты — легитимная */}
|
||||
{selectedSite.isLegit ? (
|
||||
<div style={s.legitPayment}>
|
||||
<div style={s.processorBadge}>
|
||||
<span style={s.processorIcon}>🏦</span>
|
||||
<span style={s.processorName}>Оплата через {selectedSite.paymentProcessor}</span>
|
||||
<span style={s.processorSecure}>🔒 Защищено</span>
|
||||
</div>
|
||||
<p style={s.processorNote}>
|
||||
Вы будете перенаправлены на защищённую страницу банка.
|
||||
Магазин не получает данные вашей карты.
|
||||
</p>
|
||||
<div style={s.cardForm}>
|
||||
<label style={s.cardLabel}>Номер карты</label>
|
||||
<input
|
||||
style={s.cardInput}
|
||||
placeholder="0000 0000 0000 0000"
|
||||
value={cardNum}
|
||||
onChange={e => setCardNum(e.target.value)}
|
||||
maxLength={19}
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={s.cardLabel}>Срок действия</label>
|
||||
<input
|
||||
style={s.cardInput}
|
||||
placeholder="MM/YY"
|
||||
value={cardExp}
|
||||
onChange={e => setCardExp(e.target.value)}
|
||||
maxLength={5}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={s.cardLabel}>CVV</label>
|
||||
<input
|
||||
style={s.cardInput}
|
||||
placeholder="•••"
|
||||
value={cardCvv}
|
||||
onChange={e => setCardCvv(e.target.value)}
|
||||
maxLength={3}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button style={s.payBtn} onClick={handlePay}>
|
||||
Оплатить {selectedSite.price} ₽
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
/* Форма оплаты — скиммер */
|
||||
<div style={s.fakePayment}>
|
||||
<div style={s.fakeSecureBadge}>
|
||||
<span>🔒</span>
|
||||
<span>Безопасная оплата картой</span>
|
||||
<span style={s.visaBadge}>VISA</span>
|
||||
<span style={s.mcBadge}>MC</span>
|
||||
</div>
|
||||
<div style={s.cardForm}>
|
||||
<label style={s.cardLabel}>Номер карты</label>
|
||||
<input
|
||||
style={s.cardInput}
|
||||
placeholder="0000 0000 0000 0000"
|
||||
value={cardNum}
|
||||
onChange={e => setCardNum(e.target.value)}
|
||||
maxLength={19}
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={s.cardLabel}>Срок действия</label>
|
||||
<input
|
||||
style={s.cardInput}
|
||||
placeholder="MM/YY"
|
||||
value={cardExp}
|
||||
onChange={e => setCardExp(e.target.value)}
|
||||
maxLength={5}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={s.cardLabel}>CVV</label>
|
||||
<input
|
||||
style={s.cardInput}
|
||||
placeholder="•••"
|
||||
value={cardCvv}
|
||||
onChange={e => setCardCvv(e.target.value)}
|
||||
maxLength={3}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button style={s.payBtnFake} onClick={handlePay}>
|
||||
Оплатить {selectedSite.price} ₽
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* === МОДАЛ: СКИММЕР === */}
|
||||
{modal === 'skimmer' && 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.domain}</b>,
|
||||
который использует <b>веб-скиммер</b> — вредоносный скрипт, перехватывающий
|
||||
платёжные данные прямо на странице оплаты.
|
||||
</div>
|
||||
|
||||
<div style={s.skimmerExplain}>
|
||||
<div style={s.skimmerExplainTitle}>Что такое скимминг?</div>
|
||||
<div style={s.skimmerExplainText}>
|
||||
Скиммер — код, встроенный в страницу оплаты. Пока вы вводите номер карты,
|
||||
он тайно копирует данные и отправляет злоумышленнику. Внешне страница
|
||||
выглядит как обычная форма оплаты.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={s.skimmerReason}>
|
||||
<div style={s.skimmerReasonTitle}>Почему этот сайт подозрителен:</div>
|
||||
<div style={s.skimmerReasonText}>{selectedSite.skimmerHint}</div>
|
||||
</div>
|
||||
|
||||
<div style={s.safeRules}>
|
||||
<div style={s.safeRulesTitle}>Как защититься:</div>
|
||||
<ul style={s.safeRulesList}>
|
||||
<li>Платите только на известных площадках (Ozon, Wildberries, DNS и т.п.)</li>
|
||||
<li>Ищите перенаправление на страницу банка (СберПей, Тинькофф и др.)</li>
|
||||
<li>Проверяйте HTTPS и домен перед вводом карты</li>
|
||||
<li>Используйте виртуальную карту с лимитом для онлайн-покупок</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button style={s.retryBtn} onClick={resetToSearch}>
|
||||
← Вернуться к поиску и выбрать другой магазин
|
||||
</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.paymentProcessor}</b>.
|
||||
Магазин <b>{selectedSite.domain}</b> не получил данные вашей карты —
|
||||
они были переданы напрямую банку по зашифрованному соединению.
|
||||
</div>
|
||||
<div style={s.successDetails}>
|
||||
<div style={s.successRow}>
|
||||
<span>Товар:</span>
|
||||
<span>{selectedSite.productName}</span>
|
||||
</div>
|
||||
<div style={s.successRow}>
|
||||
<span>Сумма:</span>
|
||||
<span style={{ color: '#00FF41' }}>{selectedSite.price} ₽</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>
|
||||
);
|
||||
};
|
||||
|
||||
const s: Record<string, React.CSSProperties> = {
|
||||
root: {
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(135deg, #0a0a14 0%, #0d1117 60%, #0a1400 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 },
|
||||
httpWarnText: { color: '#f5a623', fontSize: 11, marginLeft: 4 },
|
||||
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,
|
||||
gap: 0,
|
||||
},
|
||||
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',
|
||||
},
|
||||
searchResultsHeader: {
|
||||
background: '#080818',
|
||||
borderBottom: '1px solid rgba(0,255,255,0.1)',
|
||||
padding: '10px 16px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
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',
|
||||
transition: 'background 0.15s',
|
||||
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,
|
||||
},
|
||||
resultPrice: {
|
||||
color: '#00FF41',
|
||||
fontSize: 13,
|
||||
marginTop: 4,
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
},
|
||||
|
||||
/* Страница товара */
|
||||
productPage: {
|
||||
padding: 24,
|
||||
},
|
||||
shopHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
marginBottom: 20,
|
||||
paddingBottom: 12,
|
||||
borderBottom: '1px solid rgba(255,255,255,0.06)',
|
||||
},
|
||||
shopName: {
|
||||
color: '#00FFFF',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
},
|
||||
unsafeBadge: {
|
||||
background: 'rgba(245,166,35,0.15)',
|
||||
border: '1px solid #f5a623',
|
||||
color: '#f5a623',
|
||||
fontSize: 11,
|
||||
padding: '3px 10px',
|
||||
borderRadius: 4,
|
||||
},
|
||||
productCard: {
|
||||
display: 'flex',
|
||||
gap: 24,
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius: 8,
|
||||
padding: 20,
|
||||
marginBottom: 20,
|
||||
},
|
||||
productImage: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
},
|
||||
productInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
productTitle: {
|
||||
color: '#ddd',
|
||||
fontSize: 16,
|
||||
marginBottom: 8,
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
productPriceTag: {
|
||||
color: '#00FF41',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 22,
|
||||
fontWeight: 700,
|
||||
marginBottom: 8,
|
||||
},
|
||||
productDesc: {
|
||||
color: '#666',
|
||||
fontSize: 12,
|
||||
marginBottom: 16,
|
||||
},
|
||||
buyBtn: {
|
||||
padding: '10px 28px',
|
||||
background: '#00FFFF',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
color: '#000',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
suspiciousHints: {
|
||||
background: 'rgba(245,166,35,0.06)',
|
||||
border: '1px solid rgba(245,166,35,0.3)',
|
||||
borderRadius: 8,
|
||||
padding: '14px 18px',
|
||||
marginTop: 16,
|
||||
},
|
||||
hintTitle: {
|
||||
color: '#f5a623',
|
||||
fontSize: 13,
|
||||
marginBottom: 8,
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontWeight: 700,
|
||||
},
|
||||
legitHints: {
|
||||
background: 'rgba(0,255,65,0.05)',
|
||||
border: '1px solid rgba(0,255,65,0.25)',
|
||||
borderRadius: 8,
|
||||
padding: '14px 18px',
|
||||
marginTop: 16,
|
||||
},
|
||||
hintTitleGood: {
|
||||
color: '#00FF41',
|
||||
fontSize: 13,
|
||||
marginBottom: 8,
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontWeight: 700,
|
||||
},
|
||||
hintList: {
|
||||
margin: 0,
|
||||
paddingLeft: 20,
|
||||
color: '#aaa',
|
||||
fontSize: 13,
|
||||
lineHeight: 1.8,
|
||||
},
|
||||
|
||||
/* Страница оплаты */
|
||||
paymentPage: {
|
||||
padding: 24,
|
||||
maxWidth: 480,
|
||||
margin: '0 auto',
|
||||
},
|
||||
paymentTitle: {
|
||||
color: '#00FFFF',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
orderSummary: {
|
||||
background: 'rgba(0,255,255,0.04)',
|
||||
border: '1px solid rgba(0,255,255,0.15)',
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
marginBottom: 20,
|
||||
},
|
||||
orderRow: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: '4px 0',
|
||||
fontSize: 13,
|
||||
},
|
||||
orderLabel: { color: '#666' },
|
||||
orderValue: { color: '#ddd' },
|
||||
legitPayment: {
|
||||
background: 'rgba(0,255,65,0.04)',
|
||||
border: '1px solid rgba(0,255,65,0.2)',
|
||||
borderRadius: 8,
|
||||
padding: 20,
|
||||
},
|
||||
processorBadge: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
marginBottom: 10,
|
||||
padding: '8px 14px',
|
||||
background: 'rgba(0,255,65,0.08)',
|
||||
borderRadius: 6,
|
||||
},
|
||||
processorIcon: { fontSize: 20 },
|
||||
processorName: { color: '#00FF41', fontFamily: "'Orbitron', monospace", fontSize: 13, fontWeight: 700, flex: 1 },
|
||||
processorSecure: { color: '#00FF41', fontSize: 12 },
|
||||
processorNote: {
|
||||
color: '#777',
|
||||
fontSize: 12,
|
||||
lineHeight: 1.6,
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
fakePayment: {
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
borderRadius: 8,
|
||||
padding: 20,
|
||||
},
|
||||
fakeSecureBadge: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginBottom: 16,
|
||||
color: '#888',
|
||||
fontSize: 12,
|
||||
},
|
||||
visaBadge: {
|
||||
background: '#1a1a6e',
|
||||
color: '#fff',
|
||||
padding: '1px 6px',
|
||||
borderRadius: 3,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
},
|
||||
mcBadge: {
|
||||
background: '#8b0000',
|
||||
color: '#fff',
|
||||
padding: '1px 6px',
|
||||
borderRadius: 3,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
},
|
||||
cardForm: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 12,
|
||||
marginBottom: 16,
|
||||
},
|
||||
cardLabel: {
|
||||
display: 'block',
|
||||
color: '#666',
|
||||
fontSize: 11,
|
||||
marginBottom: 4,
|
||||
fontFamily: "'Share Tech Mono', monospace",
|
||||
},
|
||||
cardInput: {
|
||||
width: '100%',
|
||||
padding: '9px 12px',
|
||||
background: '#0a0a1a',
|
||||
border: '1px solid rgba(0,255,255,0.2)',
|
||||
borderRadius: 5,
|
||||
color: '#ddd',
|
||||
fontSize: 14,
|
||||
fontFamily: "'Share Tech Mono', monospace",
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
payBtn: {
|
||||
width: '100%',
|
||||
padding: '12px',
|
||||
background: '#00FF41',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
color: '#000',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
payBtnFake: {
|
||||
width: '100%',
|
||||
padding: '12px',
|
||||
background: '#2255cc',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
color: '#fff',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
|
||||
/* Модалки */
|
||||
modalOverlay: {
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
background: 'rgba(0,0,0,0.8)',
|
||||
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,
|
||||
},
|
||||
skimmerExplain: {
|
||||
background: 'rgba(255,0,64,0.06)',
|
||||
border: '1px solid rgba(255,0,64,0.25)',
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
marginBottom: 12,
|
||||
textAlign: 'left',
|
||||
},
|
||||
skimmerExplainTitle: {
|
||||
color: '#FF0040',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
marginBottom: 6,
|
||||
},
|
||||
skimmerExplainText: {
|
||||
color: '#aaa',
|
||||
fontSize: 13,
|
||||
lineHeight: 1.6,
|
||||
},
|
||||
skimmerReason: {
|
||||
background: 'rgba(245,166,35,0.06)',
|
||||
border: '1px solid rgba(245,166,35,0.25)',
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
marginBottom: 12,
|
||||
textAlign: 'left',
|
||||
},
|
||||
skimmerReasonTitle: {
|
||||
color: '#f5a623',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
marginBottom: 6,
|
||||
},
|
||||
skimmerReasonText: {
|
||||
color: '#aaa',
|
||||
fontSize: 13,
|
||||
lineHeight: 1.6,
|
||||
},
|
||||
safeRules: {
|
||||
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',
|
||||
},
|
||||
safeRulesTitle: {
|
||||
color: '#00FF41',
|
||||
fontFamily: "'Orbitron', monospace",
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
marginBottom: 8,
|
||||
},
|
||||
safeRulesList: {
|
||||
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 Case5Desktop;
|
||||
|
|
@ -311,11 +311,49 @@
|
|||
.csm-level-4 .csm-tooltip::after { border-left-color: var(--csm-cyan); }
|
||||
.csm-level-4 .csm-tooltip-title { color: var(--csm-cyan); }
|
||||
|
||||
.csm-deepfake:hover .csm-hexagon-bg { stroke: #FF0040; filter: drop-shadow(0 0 20px #FF0040); }
|
||||
.csm-deepfake:hover .csm-hexagon-icon { color: #FF0040; }
|
||||
.csm-deepfake .csm-tooltip { border-color: #FF0040; }
|
||||
.csm-deepfake .csm-tooltip::after { border-left-color: #FF0040; }
|
||||
.csm-deepfake .csm-tooltip-title { color: #FF0040; }
|
||||
@keyframes csm-rainbow-stroke {
|
||||
0% { stroke: #00FF41; filter: drop-shadow(0 0 20px #00FF41); }
|
||||
25% { stroke: #00FFFF; filter: drop-shadow(0 0 20px #00FFFF); }
|
||||
50% { stroke: #FF00FF; filter: drop-shadow(0 0 20px #FF00FF); }
|
||||
75% { stroke: #FF6600; filter: drop-shadow(0 0 20px #FF6600); }
|
||||
100% { stroke: #00FF41; filter: drop-shadow(0 0 20px #00FF41); }
|
||||
}
|
||||
@keyframes csm-rainbow-color {
|
||||
0% { color: #00FF41; }
|
||||
25% { color: #00FFFF; }
|
||||
50% { color: #FF00FF; }
|
||||
75% { color: #FF6600; }
|
||||
100% { color: #00FF41; }
|
||||
}
|
||||
@keyframes csm-rainbow-border {
|
||||
0% { border-color: #00FF41; }
|
||||
25% { border-color: #00FFFF; }
|
||||
50% { border-color: #FF00FF; }
|
||||
75% { border-color: #FF6600; }
|
||||
100% { border-color: #00FF41; }
|
||||
}
|
||||
|
||||
.csm-deepfake .csm-hexagon-bg {
|
||||
animation: csm-rainbow-stroke 3s linear infinite;
|
||||
}
|
||||
.csm-deepfake .csm-hexagon-icon {
|
||||
animation: csm-rainbow-color 3s linear infinite;
|
||||
}
|
||||
.csm-deepfake:hover .csm-hexagon-bg { animation-duration: 1s; }
|
||||
.csm-deepfake:hover .csm-hexagon-icon { animation-duration: 1s; }
|
||||
.csm-deepfake .csm-tooltip {
|
||||
animation: csm-rainbow-border 3s linear infinite;
|
||||
}
|
||||
.csm-deepfake .csm-tooltip::after { border-left-color: #FF00FF; }
|
||||
.csm-deepfake .csm-tooltip-title {
|
||||
animation: csm-rainbow-color 3s linear infinite;
|
||||
}
|
||||
|
||||
.csm-level-5:hover .csm-hexagon-bg { stroke: #a855f7; filter: drop-shadow(0 0 20px #a855f7); }
|
||||
.csm-level-5:hover .csm-hexagon-icon { color: #a855f7; }
|
||||
.csm-level-5 .csm-tooltip { border-color: #a855f7; }
|
||||
.csm-level-5 .csm-tooltip::after { border-left-color: #a855f7; }
|
||||
.csm-level-5 .csm-tooltip-title { color: #a855f7; }
|
||||
|
||||
/* Bottom Container */
|
||||
.csm-bottom-container {
|
||||
|
|
@ -325,7 +363,7 @@
|
|||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 80px;
|
||||
padding: 0 140px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,13 +35,15 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
|||
|
||||
const selectLevel = useCallback((level: number | string) => {
|
||||
const levelNames: Record<string | number, string> = {
|
||||
1: 'Phishing – Fake emails & links',
|
||||
2: 'Skimming – ATM/card fraud',
|
||||
3: 'Password cracking – Brute force & dictionary',
|
||||
4: 'Social engineering – Manipulation',
|
||||
'deepfake': 'Voice deepfake – AI-generated speech',
|
||||
1: 'Устаревшее ПО — обновление системы',
|
||||
2: 'Рабочая среда — базовые угрозы',
|
||||
3: 'Фишинг — поддельное письмо Госуслуг',
|
||||
4: 'Публичная сеть — защита через VPN',
|
||||
5: 'Веб-скимминг — поддельный магазин',
|
||||
'deepfake': 'Дипфейк — ИИ-генерация голоса',
|
||||
};
|
||||
showNotification(`>> LEVEL ${level} SELECTED`, levelNames[level]);
|
||||
const label = typeof level === 'number' ? `УРОВЕНЬ ${level}` : level.toUpperCase();
|
||||
showNotification(`>> ${label} ВЫБРАН`, levelNames[level]);
|
||||
onSelectLevel?.(level);
|
||||
}, [showNotification, onSelectLevel]);
|
||||
|
||||
|
|
@ -63,7 +65,8 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
|||
'2': () => selectLevel(2),
|
||||
'3': () => selectLevel(3),
|
||||
'4': () => selectLevel(4),
|
||||
'5': () => selectLevel('deepfake'),
|
||||
'5': () => selectLevel(5),
|
||||
'6': () => selectLevel('deepfake'),
|
||||
'w': openWiki, 'W': openWiki,
|
||||
'q': openQuiz, 'Q': openQuiz,
|
||||
};
|
||||
|
|
@ -209,11 +212,12 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
|||
}, []);
|
||||
|
||||
const menuItems = [
|
||||
{ level: 1 as number | string, cls: 'csm-level-1', icon: '🔒', title: 'Level 1', desc: 'Phishing – Fake emails & links' },
|
||||
{ level: 2 as number | string, cls: 'csm-level-2', icon: '⌨️', title: 'Level 2', desc: 'Skimming – ATM/card fraud' },
|
||||
{ level: 3 as number | string, cls: 'csm-level-3', icon: '🖥️', title: 'Level 3', desc: 'Password cracking – Brute force & dictionary' },
|
||||
{ level: 4 as number | string, cls: 'csm-level-4', icon: '🛡️', title: 'Level 4', desc: 'Social engineering – Manipulation' },
|
||||
{ level: 'deepfake', cls: 'csm-deepfake', icon: '🎭', title: 'Deepfake', desc: 'Voice deepfake – AI-generated speech' },
|
||||
{ level: 1 as number | string, cls: 'csm-level-1', icon: '💻', title: 'Уровень 1', desc: 'Устаревшее ПО — обновление системы' },
|
||||
{ level: 2 as number | string, cls: 'csm-level-2', icon: '🖥️', title: 'Уровень 2', desc: 'Рабочая среда — базовые угрозы' },
|
||||
{ level: 3 as number | string, cls: 'csm-level-3', icon: '📧', title: 'Уровень 3', desc: 'Фишинг — поддельное письмо Госуслуг' },
|
||||
{ level: 4 as number | string, cls: 'csm-level-4', icon: '📶', title: 'Уровень 4', desc: 'Публичная сеть — защита через VPN' },
|
||||
{ level: 5 as number | string, cls: 'csm-level-5', icon: '💳', title: 'Уровень 5', desc: 'Веб-скимминг — поддельный магазин' },
|
||||
{ level: 'deepfake', cls: 'csm-deepfake', icon: '🎭', title: 'Дипфейк', desc: 'Дипфейк — ИИ-генерация голоса' },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
@ -344,14 +348,14 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
|||
<span className="csm-block-icon">📖</span>
|
||||
<span className="csm-block-text">WIKI</span>
|
||||
</div>
|
||||
<div className="csm-block-tooltip">Security knowledge base</div>
|
||||
<div className="csm-block-tooltip">База знаний по безопастности</div>
|
||||
</div>
|
||||
<div className="csm-bottom-block" onClick={openQuiz}>
|
||||
<div className="csm-block-shape">
|
||||
<span className="csm-block-icon">❓</span>
|
||||
<span className="csm-block-text">QUIZ</span>
|
||||
</div>
|
||||
<div className="csm-block-tooltip">Test your skills</div>
|
||||
<div className="csm-block-tooltip">Проверь свои знания!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,39 @@
|
|||
.chatterbox-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
background: #0d0d22;
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
border: 1px solid rgba(0,255,255,0.2);
|
||||
box-shadow: 0 0 40px rgba(0,255,255,0.06);
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
}
|
||||
|
||||
.chatterbox-container p {
|
||||
color: #aaa;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chatterbox-container h2 {
|
||||
color: #00FFFF;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin: 24px 0 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.chatterbox-container h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
color: #00FFFF;
|
||||
margin-bottom: 30px;
|
||||
font-size: 28px;
|
||||
font-size: 22px;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 0 20px rgba(0,255,255,0.5);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
|
|
@ -22,9 +43,10 @@
|
|||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
|
|
@ -33,10 +55,13 @@
|
|||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
transition: border-color 0.3s;
|
||||
background: #080818;
|
||||
border: 1px solid rgba(0,255,255,0.2);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: #ddd;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
transition: border-color 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +69,13 @@
|
|||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
border-color: #00FFFF;
|
||||
box-shadow: 0 0 8px rgba(0,255,255,0.2);
|
||||
}
|
||||
|
||||
.form-group select option {
|
||||
background: #0d0d22;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
|
|
@ -54,109 +85,119 @@
|
|||
|
||||
.char-counter {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
color: #555;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Voice section */
|
||||
.voice-section {
|
||||
background: #f8f9ff;
|
||||
border-radius: 15px;
|
||||
background: #080818;
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border: 1px solid rgba(0,255,255,0.15);
|
||||
}
|
||||
|
||||
.voice-section.recording {
|
||||
border-color: #e74c3c;
|
||||
background: #fdf2f2;
|
||||
border-color: #FF0040;
|
||||
box-shadow: 0 0 16px rgba(255,0,64,0.15);
|
||||
}
|
||||
|
||||
.voice-section.has-recording {
|
||||
border-color: #4caf50;
|
||||
background: #f1f8f4;
|
||||
border-color: #00FF41;
|
||||
box-shadow: 0 0 16px rgba(0,255,65,0.1);
|
||||
}
|
||||
|
||||
.voice-label {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
color: #00FFFF;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-family: 'Orbitron', monospace;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.check-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
background: rgba(0,255,65,0.15);
|
||||
border: 1px solid #00FF41;
|
||||
color: #00FF41;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
margin-left: 10px;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
}
|
||||
|
||||
.record-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
background: rgba(0,255,255,0.1);
|
||||
border: 2px solid #00FFFF;
|
||||
color: #00FFFF;
|
||||
border-radius: 50%;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||
box-shadow: 0 0 20px rgba(0,255,255,0.2);
|
||||
}
|
||||
|
||||
.record-btn:hover {
|
||||
transform: scale(1.1);
|
||||
background: rgba(0,255,255,0.2);
|
||||
box-shadow: 0 0 30px rgba(0,255,255,0.4);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.record-btn.recording {
|
||||
background: #e74c3c;
|
||||
animation: pulse 1.5s infinite;
|
||||
background: rgba(255,0,64,0.15);
|
||||
border-color: #FF0040;
|
||||
color: #FF0040;
|
||||
box-shadow: 0 0 20px rgba(255,0,64,0.3);
|
||||
animation: ctb-pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
@keyframes ctb-pulse {
|
||||
0%, 100% { transform: scale(1); box-shadow: 0 0 20px rgba(255,0,64,0.3); }
|
||||
50% { transform: scale(1.08); box-shadow: 0 0 35px rgba(255,0,64,0.5); }
|
||||
}
|
||||
|
||||
.record-status {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.record-timer {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
color: #FF0040;
|
||||
margin-top: 10px;
|
||||
font-family: monospace;
|
||||
font-family: 'Orbitron', monospace;
|
||||
text-shadow: 0 0 10px rgba(255,0,64,0.5);
|
||||
}
|
||||
|
||||
.voice-text {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
background: rgba(0,255,255,0.04);
|
||||
border: 1px dashed rgba(0,255,255,0.3);
|
||||
border-radius: 8px;
|
||||
padding: 16px 20px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border: 2px dashed #667eea;
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.voice-preview {
|
||||
|
|
@ -165,6 +206,7 @@
|
|||
|
||||
.voice-preview audio {
|
||||
width: 100%;
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.voice-actions {
|
||||
|
|
@ -176,30 +218,44 @@
|
|||
.voice-actions button {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-retry {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.btn-retry:hover {
|
||||
border-color: #f5a623;
|
||||
color: #f5a623;
|
||||
}
|
||||
|
||||
.btn-use {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
background: rgba(0,255,65,0.1);
|
||||
border: 1px solid #00FF41;
|
||||
color: #00FF41;
|
||||
}
|
||||
|
||||
.btn-use:hover {
|
||||
background: rgba(0,255,65,0.2);
|
||||
box-shadow: 0 0 12px rgba(0,255,65,0.3);
|
||||
}
|
||||
|
||||
/* File upload */
|
||||
.or-divider {
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
color: #444;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.or-divider::before,
|
||||
|
|
@ -209,35 +265,40 @@
|
|||
top: 50%;
|
||||
width: 40%;
|
||||
height: 1px;
|
||||
background: #e0e0e0;
|
||||
background: rgba(0,255,255,0.1);
|
||||
}
|
||||
|
||||
.or-divider::before { left: 0; }
|
||||
.or-divider::after { right: 0; }
|
||||
|
||||
.file-input-wrapper {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed rgba(0,255,255,0.2);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
transition: all 0.2s;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.file-input-wrapper:hover {
|
||||
border-color: #667eea;
|
||||
background: #f8f9ff;
|
||||
border-color: #00FFFF;
|
||||
background: rgba(0,255,255,0.04);
|
||||
color: #00FFFF;
|
||||
}
|
||||
|
||||
.file-input-wrapper.has-file {
|
||||
border-color: #4caf50;
|
||||
background: #f1f8f4;
|
||||
border-color: #00FF41;
|
||||
background: rgba(0,255,65,0.04);
|
||||
color: #00FF41;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
color: #00FFFF;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Sliders */
|
||||
|
|
@ -249,13 +310,16 @@
|
|||
|
||||
.slider-group input[type="range"] {
|
||||
flex: 1;
|
||||
accent-color: #00FFFF;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
color: #00FFFF;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.two-col {
|
||||
|
|
@ -267,24 +331,26 @@
|
|||
/* Submit */
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 14px;
|
||||
background: rgba(0,255,65,0.1);
|
||||
border: 1px solid #00FF41;
|
||||
color: #00FF41;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
letter-spacing: 2px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.submit-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
|
||||
background: rgba(0,255,65,0.2);
|
||||
box-shadow: 0 0 20px rgba(0,255,65,0.3);
|
||||
}
|
||||
|
||||
.submit-btn:disabled {
|
||||
opacity: 0.6;
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
|
@ -292,43 +358,51 @@
|
|||
.loading-block {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 2px solid rgba(0,255,255,0.15);
|
||||
border-top: 2px solid #00FFFF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
animation: ctb-spin 0.8s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
@keyframes ctb-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Error */
|
||||
.error-block {
|
||||
color: #e74c3c;
|
||||
background: #fdf2f2;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
color: #FF0040;
|
||||
background: rgba(255,0,64,0.08);
|
||||
border: 1px solid rgba(255,0,64,0.3);
|
||||
padding: 14px;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Result */
|
||||
.result-block {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
background: rgba(0,255,65,0.04);
|
||||
border: 1px solid rgba(0,255,65,0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.result-block h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
color: #00FF41;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.result-block audio {
|
||||
|
|
@ -339,10 +413,17 @@
|
|||
.download-btn {
|
||||
display: inline-block;
|
||||
margin-top: 15px;
|
||||
padding: 10px 20px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
padding: 9px 20px;
|
||||
background: rgba(0,255,65,0.1);
|
||||
border: 1px solid #00FF41;
|
||||
color: #00FF41;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
background: rgba(0,255,65,0.2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ const ChatterboxTTS = () => {
|
|||
|
||||
{showVoiceText && (
|
||||
<div className="voice-text">
|
||||
📢 Произнесите, например: <span style={{ color: '#667eea' }}>"Хакатон 2026 походим модуль deepfake, кейс Цент Инвест команда Атейкин"</span>
|
||||
📢 Произнесите, например: <span style={{ color: '#00FFFF' }}>"Хакатон 2026 походим модуль deepfake, кейс Цент Инвест команда Атейкин"</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ const ChatterboxTTS = () => {
|
|||
onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
|
||||
onDragLeave={() => setIsDragOver(false)}
|
||||
onDrop={handleDrop}
|
||||
style={isDragOver ? { borderColor: '#667eea' } : undefined}
|
||||
style={isDragOver ? { borderColor: '#00FFFF' } : undefined}
|
||||
>
|
||||
<input ref={fileInputRef} type="file" accept="audio/*" onChange={handleFileChange} style={{ display: 'none' }} />
|
||||
<div>{fileName ? '✅ Файл выбран' : '📁 Кликни или перетащи аудио файл'}</div>
|
||||
|
|
|
|||
|
|
@ -1,36 +1,44 @@
|
|||
.voice-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 20px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ── Карточка-сценарий ── */
|
||||
.voice-card {
|
||||
background: #1e1e2e;
|
||||
border-radius: 16px;
|
||||
background: #080818;
|
||||
border: 1px solid rgba(0,255,255,0.15);
|
||||
border-radius: 10px;
|
||||
padding: 16px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,.35);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,.4);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.voice-card:hover {
|
||||
border-color: rgba(0,255,255,0.3);
|
||||
}
|
||||
|
||||
.voice-card-scenario {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .08em;
|
||||
letter-spacing: .1em;
|
||||
text-transform: uppercase;
|
||||
color: #a78bfa;
|
||||
color: #00FFFF;
|
||||
font-family: 'Orbitron', monospace;
|
||||
}
|
||||
|
||||
.voice-card-quote {
|
||||
font-size: 13px;
|
||||
color: #c4c4d4;
|
||||
line-height: 1.5;
|
||||
border-left: 3px solid #7c3aed;
|
||||
color: #aaa;
|
||||
line-height: 1.6;
|
||||
border-left: 2px solid #00FFFF;
|
||||
padding-left: 10px;
|
||||
margin: 0;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
}
|
||||
|
||||
/* ── Аудиосообщение (Telegram-стиль) ── */
|
||||
|
|
@ -38,7 +46,8 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #2a2a3e;
|
||||
background: #0d0d22;
|
||||
border: 1px solid rgba(0,255,255,0.1);
|
||||
border-radius: 22px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
|
@ -49,38 +58,42 @@
|
|||
height: 40px;
|
||||
min-width: 40px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: #7c3aed;
|
||||
color: #fff;
|
||||
border: 1px solid #00FFFF;
|
||||
background: rgba(0,255,255,0.1);
|
||||
color: #00FFFF;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background .15s;
|
||||
transition: all 0.15s;
|
||||
position: relative;
|
||||
}
|
||||
.audio-msg-btn:hover { background: #6d28d9; }
|
||||
.audio-msg-btn.loading {
|
||||
background: #3f3f5a;
|
||||
cursor: default;
|
||||
animation: pulse 1.2s ease-in-out infinite;
|
||||
.audio-msg-btn:hover {
|
||||
background: rgba(0,255,255,0.2);
|
||||
box-shadow: 0 0 12px rgba(0,255,255,0.3);
|
||||
}
|
||||
@keyframes pulse {
|
||||
.audio-msg-btn.loading {
|
||||
border-color: rgba(0,255,255,0.3);
|
||||
background: rgba(0,255,255,0.05);
|
||||
cursor: default;
|
||||
animation: vc-pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes vc-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: .5; }
|
||||
50% { opacity: .4; }
|
||||
}
|
||||
|
||||
/* Спиннер внутри кнопки */
|
||||
.audio-spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(255,255,255,.3);
|
||||
border-top-color: #fff;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(0,255,255,0.2);
|
||||
border-top-color: #00FFFF;
|
||||
border-radius: 50%;
|
||||
animation: spin .7s linear infinite;
|
||||
animation: vc-spin .7s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
@keyframes vc-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* Волна + прогресс */
|
||||
.audio-msg-wave {
|
||||
|
|
@ -102,42 +115,45 @@
|
|||
.waveform-bar {
|
||||
flex: 1;
|
||||
border-radius: 2px;
|
||||
background: #4b4b6e;
|
||||
background: rgba(0,255,255,0.15);
|
||||
transition: background .1s;
|
||||
}
|
||||
.waveform-bar.played {
|
||||
background: #7c3aed;
|
||||
background: #00FFFF;
|
||||
}
|
||||
|
||||
.audio-msg-time {
|
||||
font-size: 11px;
|
||||
color: #6b6b8a;
|
||||
color: #555;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
}
|
||||
|
||||
/* Кнопка перегенерации */
|
||||
.regen-btn {
|
||||
background: none;
|
||||
border: 1px solid #3f3f5a;
|
||||
color: #a0a0c0;
|
||||
border: 1px solid rgba(0,255,255,0.2);
|
||||
color: #666;
|
||||
border-radius: 20px;
|
||||
padding: 5px 12px;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
align-self: flex-end;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
transition: border-color .15s, color .15s;
|
||||
}
|
||||
.regen-btn:hover:not(:disabled) {
|
||||
border-color: #7c3aed;
|
||||
color: #a78bfa;
|
||||
border-color: #00FFFF;
|
||||
color: #00FFFF;
|
||||
}
|
||||
.regen-btn:disabled { opacity: .4; cursor: default; }
|
||||
.regen-btn:disabled { opacity: .3; cursor: default; }
|
||||
|
||||
/* Ошибка */
|
||||
.voice-card-error {
|
||||
font-size: 12px;
|
||||
color: #f87171;
|
||||
color: #FF0040;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue