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 Case2Desktop from './cases/Case2Desktop';
|
||||||
import Case3Desktop from './cases/Case3Desktop';
|
import Case3Desktop from './cases/Case3Desktop';
|
||||||
import Case4Desktop from './cases/Case4Desktop';
|
import Case4Desktop from './cases/Case4Desktop';
|
||||||
|
import Case5Desktop from './cases/Case5Desktop';
|
||||||
import ChatterboxTTS from './components/deepfake/ChatterboxTTS';
|
import ChatterboxTTS from './components/deepfake/ChatterboxTTS';
|
||||||
import MyQuize from './components/MyQuize';
|
import MyQuize from './components/MyQuize';
|
||||||
import CyberSecurityArticle from './components/CyberSecurityArticle';
|
import CyberSecurityArticle from './components/CyberSecurityArticle';
|
||||||
|
|
||||||
export type WallpaperType = 'xp' | 'win7' | 'win10';
|
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 App: React.FC = () => {
|
||||||
const [overlay, setOverlay] = useState<OverlayType>(null);
|
const [overlay, setOverlay] = useState<OverlayType>(null);
|
||||||
|
|
@ -23,6 +24,7 @@ const App: React.FC = () => {
|
||||||
2: 'case2',
|
2: 'case2',
|
||||||
3: 'case3',
|
3: 'case3',
|
||||||
4: 'case4',
|
4: 'case4',
|
||||||
|
5: 'case5',
|
||||||
'deepfake': 'deepfake',
|
'deepfake': 'deepfake',
|
||||||
};
|
};
|
||||||
setTimeout(() => setOverlay(map[level] ?? null), 1200);
|
setTimeout(() => setOverlay(map[level] ?? null), 1200);
|
||||||
|
|
@ -56,6 +58,7 @@ const App: React.FC = () => {
|
||||||
{overlay === 'case2' && <Case2Desktop onComplete={close} />}
|
{overlay === 'case2' && <Case2Desktop onComplete={close} />}
|
||||||
{overlay === 'case3' && <Case3Desktop onComplete={close} />}
|
{overlay === 'case3' && <Case3Desktop onComplete={close} />}
|
||||||
{overlay === 'case4' && <Case4Desktop onComplete={close} />}
|
{overlay === 'case4' && <Case4Desktop onComplete={close} />}
|
||||||
|
{overlay === 'case5' && <Case5Desktop onComplete={close} />}
|
||||||
{overlay === 'deepfake' && <ChatterboxTTS />}
|
{overlay === 'deepfake' && <ChatterboxTTS />}
|
||||||
{overlay === 'quiz' && <MyQuize />}
|
{overlay === 'quiz' && <MyQuize />}
|
||||||
{overlay === 'wiki' && <CyberSecurityArticle />}
|
{overlay === 'wiki' && <CyberSecurityArticle />}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,456 @@
|
||||||
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 './Case3Desktop.css';
|
|
||||||
|
|
||||||
interface Case3DesktopProps {
|
interface Case3DesktopProps {
|
||||||
onComplete: (type: WallpaperType) => void;
|
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 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 (
|
return (
|
||||||
<div>
|
<div style={s.root}>
|
||||||
<MainApp showGosuslugi={true} showAmnezia={false}/>
|
{/* Браузер */}
|
||||||
|
<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>
|
</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;
|
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::after { border-left-color: var(--csm-cyan); }
|
||||||
.csm-level-4 .csm-tooltip-title { 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); }
|
@keyframes csm-rainbow-stroke {
|
||||||
.csm-deepfake:hover .csm-hexagon-icon { color: #FF0040; }
|
0% { stroke: #00FF41; filter: drop-shadow(0 0 20px #00FF41); }
|
||||||
.csm-deepfake .csm-tooltip { border-color: #FF0040; }
|
25% { stroke: #00FFFF; filter: drop-shadow(0 0 20px #00FFFF); }
|
||||||
.csm-deepfake .csm-tooltip::after { border-left-color: #FF0040; }
|
50% { stroke: #FF00FF; filter: drop-shadow(0 0 20px #FF00FF); }
|
||||||
.csm-deepfake .csm-tooltip-title { color: #FF0040; }
|
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 */
|
/* Bottom Container */
|
||||||
.csm-bottom-container {
|
.csm-bottom-container {
|
||||||
|
|
@ -325,7 +363,7 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 80px;
|
padding: 0 140px;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,15 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
||||||
|
|
||||||
const selectLevel = useCallback((level: number | string) => {
|
const selectLevel = useCallback((level: number | string) => {
|
||||||
const levelNames: Record<string | number, string> = {
|
const levelNames: Record<string | number, string> = {
|
||||||
1: 'Phishing – Fake emails & links',
|
1: 'Устаревшее ПО — обновление системы',
|
||||||
2: 'Skimming – ATM/card fraud',
|
2: 'Рабочая среда — базовые угрозы',
|
||||||
3: 'Password cracking – Brute force & dictionary',
|
3: 'Фишинг — поддельное письмо Госуслуг',
|
||||||
4: 'Social engineering – Manipulation',
|
4: 'Публичная сеть — защита через VPN',
|
||||||
'deepfake': 'Voice deepfake – AI-generated speech',
|
5: 'Веб-скимминг — поддельный магазин',
|
||||||
|
'deepfake': 'Дипфейк — ИИ-генерация голоса',
|
||||||
};
|
};
|
||||||
showNotification(`>> LEVEL ${level} SELECTED`, levelNames[level]);
|
const label = typeof level === 'number' ? `УРОВЕНЬ ${level}` : level.toUpperCase();
|
||||||
|
showNotification(`>> ${label} ВЫБРАН`, levelNames[level]);
|
||||||
onSelectLevel?.(level);
|
onSelectLevel?.(level);
|
||||||
}, [showNotification, onSelectLevel]);
|
}, [showNotification, onSelectLevel]);
|
||||||
|
|
||||||
|
|
@ -63,7 +65,8 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
||||||
'2': () => selectLevel(2),
|
'2': () => selectLevel(2),
|
||||||
'3': () => selectLevel(3),
|
'3': () => selectLevel(3),
|
||||||
'4': () => selectLevel(4),
|
'4': () => selectLevel(4),
|
||||||
'5': () => selectLevel('deepfake'),
|
'5': () => selectLevel(5),
|
||||||
|
'6': () => selectLevel('deepfake'),
|
||||||
'w': openWiki, 'W': openWiki,
|
'w': openWiki, 'W': openWiki,
|
||||||
'q': openQuiz, 'Q': openQuiz,
|
'q': openQuiz, 'Q': openQuiz,
|
||||||
};
|
};
|
||||||
|
|
@ -209,11 +212,12 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ level: 1 as number | string, cls: 'csm-level-1', icon: '🔒', title: 'Level 1', desc: 'Phishing – Fake emails & links' },
|
{ level: 1 as number | string, cls: 'csm-level-1', icon: '💻', title: 'Уровень 1', desc: 'Устаревшее ПО — обновление системы' },
|
||||||
{ level: 2 as number | string, cls: 'csm-level-2', icon: '⌨️', title: 'Level 2', desc: 'Skimming – ATM/card fraud' },
|
{ level: 2 as number | string, cls: 'csm-level-2', icon: '🖥️', title: 'Уровень 2', desc: 'Рабочая среда — базовые угрозы' },
|
||||||
{ level: 3 as number | string, cls: 'csm-level-3', icon: '🖥️', title: 'Level 3', desc: 'Password cracking – Brute force & dictionary' },
|
{ level: 3 as number | string, cls: 'csm-level-3', icon: '📧', title: 'Уровень 3', desc: 'Фишинг — поддельное письмо Госуслуг' },
|
||||||
{ level: 4 as number | string, cls: 'csm-level-4', icon: '🛡️', title: 'Level 4', desc: 'Social engineering – Manipulation' },
|
{ level: 4 as number | string, cls: 'csm-level-4', icon: '📶', title: 'Уровень 4', desc: 'Публичная сеть — защита через VPN' },
|
||||||
{ level: 'deepfake', cls: 'csm-deepfake', icon: '🎭', title: 'Deepfake', desc: 'Voice deepfake – AI-generated speech' },
|
{ level: 5 as number | string, cls: 'csm-level-5', icon: '💳', title: 'Уровень 5', desc: 'Веб-скимминг — поддельный магазин' },
|
||||||
|
{ level: 'deepfake', cls: 'csm-deepfake', icon: '🎭', title: 'Дипфейк', desc: 'Дипфейк — ИИ-генерация голоса' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -344,14 +348,14 @@ const CybersecMenu: React.FC<CybersecMenuProps> = ({ onSelectLevel, onOpenWiki,
|
||||||
<span className="csm-block-icon">📖</span>
|
<span className="csm-block-icon">📖</span>
|
||||||
<span className="csm-block-text">WIKI</span>
|
<span className="csm-block-text">WIKI</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="csm-block-tooltip">Security knowledge base</div>
|
<div className="csm-block-tooltip">База знаний по безопастности</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="csm-bottom-block" onClick={openQuiz}>
|
<div className="csm-bottom-block" onClick={openQuiz}>
|
||||||
<div className="csm-block-shape">
|
<div className="csm-block-shape">
|
||||||
<span className="csm-block-icon">❓</span>
|
<span className="csm-block-icon">❓</span>
|
||||||
<span className="csm-block-text">QUIZ</span>
|
<span className="csm-block-text">QUIZ</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="csm-block-tooltip">Test your skills</div>
|
<div className="csm-block-tooltip">Проверь свои знания!</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,39 @@
|
||||||
.chatterbox-container {
|
.chatterbox-container {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: white;
|
background: #0d0d22;
|
||||||
border-radius: 20px;
|
border-radius: 12px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
border: 1px solid rgba(0,255,255,0.2);
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
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 {
|
.chatterbox-container h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #333;
|
color: #00FFFF;
|
||||||
margin-bottom: 30px;
|
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 {
|
.form-group {
|
||||||
|
|
@ -22,9 +43,10 @@
|
||||||
.form-group label {
|
.form-group label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: #555;
|
color: #888;
|
||||||
font-weight: 500;
|
font-size: 12px;
|
||||||
font-size: 14px;
|
letter-spacing: 1px;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input[type="text"],
|
.form-group input[type="text"],
|
||||||
|
|
@ -33,10 +55,13 @@
|
||||||
.form-group textarea {
|
.form-group textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border: 2px solid #e0e0e0;
|
background: #080818;
|
||||||
border-radius: 10px;
|
border: 1px solid rgba(0,255,255,0.2);
|
||||||
font-size: 15px;
|
border-radius: 6px;
|
||||||
transition: border-color 0.3s;
|
font-size: 14px;
|
||||||
|
color: #ddd;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
|
transition: border-color 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +69,13 @@
|
||||||
.form-group select:focus,
|
.form-group select:focus,
|
||||||
.form-group textarea:focus {
|
.form-group textarea:focus {
|
||||||
outline: none;
|
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 {
|
.form-group textarea {
|
||||||
|
|
@ -54,109 +85,119 @@
|
||||||
|
|
||||||
.char-counter {
|
.char-counter {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: #999;
|
color: #555;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Voice section */
|
/* Voice section */
|
||||||
.voice-section {
|
.voice-section {
|
||||||
background: #f8f9ff;
|
background: #080818;
|
||||||
border-radius: 15px;
|
border-radius: 8px;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
border: 2px solid #e0e0e0;
|
border: 1px solid rgba(0,255,255,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-section.recording {
|
.voice-section.recording {
|
||||||
border-color: #e74c3c;
|
border-color: #FF0040;
|
||||||
background: #fdf2f2;
|
box-shadow: 0 0 16px rgba(255,0,64,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-section.has-recording {
|
.voice-section.has-recording {
|
||||||
border-color: #4caf50;
|
border-color: #00FF41;
|
||||||
background: #f1f8f4;
|
box-shadow: 0 0 16px rgba(0,255,65,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-label {
|
.voice-label {
|
||||||
font-size: 16px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #00FFFF;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
font-family: 'Orbitron', monospace;
|
||||||
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-badge {
|
.check-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
background: #4caf50;
|
background: rgba(0,255,65,0.15);
|
||||||
color: white;
|
border: 1px solid #00FF41;
|
||||||
padding: 4px 12px;
|
color: #00FF41;
|
||||||
|
padding: 3px 10px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-btn {
|
.record-btn {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: rgba(0,255,255,0.1);
|
||||||
color: white;
|
border: 2px solid #00FFFF;
|
||||||
border: none;
|
color: #00FFFF;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.2s;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 0 auto;
|
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 {
|
.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 {
|
.record-btn.recording {
|
||||||
background: #e74c3c;
|
background: rgba(255,0,64,0.15);
|
||||||
animation: pulse 1.5s infinite;
|
border-color: #FF0040;
|
||||||
|
color: #FF0040;
|
||||||
|
box-shadow: 0 0 20px rgba(255,0,64,0.3);
|
||||||
|
animation: ctb-pulse 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes ctb-pulse {
|
||||||
0%, 100% { transform: scale(1); }
|
0%, 100% { transform: scale(1); box-shadow: 0 0 20px rgba(255,0,64,0.3); }
|
||||||
50% { transform: scale(1.1); }
|
50% { transform: scale(1.08); box-shadow: 0 0 35px rgba(255,0,64,0.5); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-status {
|
.record-status {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-timer {
|
.record-timer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #667eea;
|
color: #FF0040;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-family: monospace;
|
font-family: 'Orbitron', monospace;
|
||||||
|
text-shadow: 0 0 10px rgba(255,0,64,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-text {
|
.voice-text {
|
||||||
background: white;
|
background: rgba(0,255,255,0.04);
|
||||||
border-radius: 10px;
|
border: 1px dashed rgba(0,255,255,0.3);
|
||||||
padding: 20px;
|
border-radius: 8px;
|
||||||
|
padding: 16px 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
color: #aaa;
|
||||||
color: #333;
|
line-height: 1.6;
|
||||||
border: 2px dashed #667eea;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-preview {
|
.voice-preview {
|
||||||
|
|
@ -165,6 +206,7 @@
|
||||||
|
|
||||||
.voice-preview audio {
|
.voice-preview audio {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
filter: invert(1) hue-rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-actions {
|
.voice-actions {
|
||||||
|
|
@ -176,30 +218,44 @@
|
||||||
.voice-actions button {
|
.voice-actions button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-retry {
|
.btn-retry {
|
||||||
background: #e0e0e0;
|
background: transparent;
|
||||||
color: #333;
|
border: 1px solid rgba(255,255,255,0.15);
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-retry:hover {
|
||||||
|
border-color: #f5a623;
|
||||||
|
color: #f5a623;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-use {
|
.btn-use {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: rgba(0,255,65,0.1);
|
||||||
color: white;
|
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 */
|
/* File upload */
|
||||||
.or-divider {
|
.or-divider {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
color: #999;
|
color: #444;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.or-divider::before,
|
.or-divider::before,
|
||||||
|
|
@ -209,35 +265,40 @@
|
||||||
top: 50%;
|
top: 50%;
|
||||||
width: 40%;
|
width: 40%;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: #e0e0e0;
|
background: rgba(0,255,255,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.or-divider::before { left: 0; }
|
.or-divider::before { left: 0; }
|
||||||
.or-divider::after { right: 0; }
|
.or-divider::after { right: 0; }
|
||||||
|
|
||||||
.file-input-wrapper {
|
.file-input-wrapper {
|
||||||
border: 2px dashed #ccc;
|
border: 1px dashed rgba(0,255,255,0.2);
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.2s;
|
||||||
|
color: #666;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-input-wrapper:hover {
|
.file-input-wrapper:hover {
|
||||||
border-color: #667eea;
|
border-color: #00FFFF;
|
||||||
background: #f8f9ff;
|
background: rgba(0,255,255,0.04);
|
||||||
|
color: #00FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-input-wrapper.has-file {
|
.file-input-wrapper.has-file {
|
||||||
border-color: #4caf50;
|
border-color: #00FF41;
|
||||||
background: #f1f8f4;
|
background: rgba(0,255,65,0.04);
|
||||||
|
color: #00FF41;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #667eea;
|
color: #00FFFF;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sliders */
|
/* Sliders */
|
||||||
|
|
@ -249,13 +310,16 @@
|
||||||
|
|
||||||
.slider-group input[type="range"] {
|
.slider-group input[type="range"] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
accent-color: #00FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-value {
|
.slider-value {
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #667eea;
|
color: #00FFFF;
|
||||||
|
font-family: 'Orbitron', monospace;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.two-col {
|
.two-col {
|
||||||
|
|
@ -267,24 +331,26 @@
|
||||||
/* Submit */
|
/* Submit */
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16px;
|
padding: 14px;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: rgba(0,255,65,0.1);
|
||||||
color: white;
|
border: 1px solid #00FF41;
|
||||||
border: none;
|
color: #00FF41;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-family: 'Orbitron', monospace;
|
||||||
|
font-weight: 700;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
letter-spacing: 2px;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn:hover:not(:disabled) {
|
.submit-btn:hover:not(:disabled) {
|
||||||
transform: translateY(-2px);
|
background: rgba(0,255,65,0.2);
|
||||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
|
box-shadow: 0 0 20px rgba(0,255,65,0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn:disabled {
|
.submit-btn:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.4;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,43 +358,51 @@
|
||||||
.loading-block {
|
.loading-block {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
border: 4px solid #f3f3f3;
|
border: 2px solid rgba(0,255,255,0.15);
|
||||||
border-top: 4px solid #667eea;
|
border-top: 2px solid #00FFFF;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: ctb-spin 0.8s linear infinite;
|
||||||
margin: 0 auto 15px;
|
margin: 0 auto 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes ctb-spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Error */
|
/* Error */
|
||||||
.error-block {
|
.error-block {
|
||||||
color: #e74c3c;
|
color: #FF0040;
|
||||||
background: #fdf2f2;
|
background: rgba(255,0,64,0.08);
|
||||||
padding: 15px;
|
border: 1px solid rgba(255,0,64,0.3);
|
||||||
border-radius: 8px;
|
padding: 14px;
|
||||||
|
border-radius: 6px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Result */
|
/* Result */
|
||||||
.result-block {
|
.result-block {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #f8f9fa;
|
background: rgba(0,255,65,0.04);
|
||||||
border-radius: 10px;
|
border: 1px solid rgba(0,255,65,0.2);
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-block h3 {
|
.result-block h3 {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
color: #333;
|
color: #00FF41;
|
||||||
|
font-family: 'Orbitron', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-block audio {
|
.result-block audio {
|
||||||
|
|
@ -339,10 +413,17 @@
|
||||||
.download-btn {
|
.download-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
padding: 10px 20px;
|
padding: 9px 20px;
|
||||||
background: #4caf50;
|
background: rgba(0,255,65,0.1);
|
||||||
color: white;
|
border: 1px solid #00FF41;
|
||||||
|
color: #00FF41;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
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 && (
|
{showVoiceText && (
|
||||||
<div className="voice-text">
|
<div className="voice-text">
|
||||||
📢 Произнесите, например: <span style={{ color: '#667eea' }}>"Хакатон 2026 походим модуль deepfake, кейс Цент Инвест команда Атейкин"</span>
|
📢 Произнесите, например: <span style={{ color: '#00FFFF' }}>"Хакатон 2026 походим модуль deepfake, кейс Цент Инвест команда Атейкин"</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -274,7 +274,7 @@ const ChatterboxTTS = () => {
|
||||||
onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
|
onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
|
||||||
onDragLeave={() => setIsDragOver(false)}
|
onDragLeave={() => setIsDragOver(false)}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
style={isDragOver ? { borderColor: '#667eea' } : undefined}
|
style={isDragOver ? { borderColor: '#00FFFF' } : undefined}
|
||||||
>
|
>
|
||||||
<input ref={fileInputRef} type="file" accept="audio/*" onChange={handleFileChange} style={{ display: 'none' }} />
|
<input ref={fileInputRef} type="file" accept="audio/*" onChange={handleFileChange} style={{ display: 'none' }} />
|
||||||
<div>{fileName ? '✅ Файл выбран' : '📁 Кликни или перетащи аудио файл'}</div>
|
<div>{fileName ? '✅ Файл выбран' : '📁 Кликни или перетащи аудио файл'}</div>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,44 @@
|
||||||
.voice-cards {
|
.voice-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 20px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Карточка-сценарий ── */
|
/* ── Карточка-сценарий ── */
|
||||||
.voice-card {
|
.voice-card {
|
||||||
background: #1e1e2e;
|
background: #080818;
|
||||||
border-radius: 16px;
|
border: 1px solid rgba(0,255,255,0.15);
|
||||||
|
border-radius: 10px;
|
||||||
padding: 16px 18px;
|
padding: 16px 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
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 {
|
.voice-card-scenario {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: .08em;
|
letter-spacing: .1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #a78bfa;
|
color: #00FFFF;
|
||||||
|
font-family: 'Orbitron', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-card-quote {
|
.voice-card-quote {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #c4c4d4;
|
color: #aaa;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
border-left: 3px solid #7c3aed;
|
border-left: 2px solid #00FFFF;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Аудиосообщение (Telegram-стиль) ── */
|
/* ── Аудиосообщение (Telegram-стиль) ── */
|
||||||
|
|
@ -38,7 +46,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
background: #2a2a3e;
|
background: #0d0d22;
|
||||||
|
border: 1px solid rgba(0,255,255,0.1);
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
}
|
}
|
||||||
|
|
@ -49,38 +58,42 @@
|
||||||
height: 40px;
|
height: 40px;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: none;
|
border: 1px solid #00FFFF;
|
||||||
background: #7c3aed;
|
background: rgba(0,255,255,0.1);
|
||||||
color: #fff;
|
color: #00FFFF;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: background .15s;
|
transition: all 0.15s;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.audio-msg-btn:hover { background: #6d28d9; }
|
.audio-msg-btn:hover {
|
||||||
.audio-msg-btn.loading {
|
background: rgba(0,255,255,0.2);
|
||||||
background: #3f3f5a;
|
box-shadow: 0 0 12px rgba(0,255,255,0.3);
|
||||||
cursor: default;
|
|
||||||
animation: pulse 1.2s ease-in-out infinite;
|
|
||||||
}
|
}
|
||||||
@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; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: .5; }
|
50% { opacity: .4; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Спиннер внутри кнопки */
|
/* Спиннер внутри кнопки */
|
||||||
.audio-spinner {
|
.audio-spinner {
|
||||||
width: 18px;
|
width: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
border: 2px solid rgba(255,255,255,.3);
|
border: 2px solid rgba(0,255,255,0.2);
|
||||||
border-top-color: #fff;
|
border-top-color: #00FFFF;
|
||||||
border-radius: 50%;
|
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 {
|
.audio-msg-wave {
|
||||||
|
|
@ -102,42 +115,45 @@
|
||||||
.waveform-bar {
|
.waveform-bar {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: #4b4b6e;
|
background: rgba(0,255,255,0.15);
|
||||||
transition: background .1s;
|
transition: background .1s;
|
||||||
}
|
}
|
||||||
.waveform-bar.played {
|
.waveform-bar.played {
|
||||||
background: #7c3aed;
|
background: #00FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio-msg-time {
|
.audio-msg-time {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #6b6b8a;
|
color: #555;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кнопка перегенерации */
|
/* Кнопка перегенерации */
|
||||||
.regen-btn {
|
.regen-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: 1px solid #3f3f5a;
|
border: 1px solid rgba(0,255,255,0.2);
|
||||||
color: #a0a0c0;
|
color: #666;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 5px 12px;
|
padding: 5px 12px;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
transition: border-color .15s, color .15s;
|
transition: border-color .15s, color .15s;
|
||||||
}
|
}
|
||||||
.regen-btn:hover:not(:disabled) {
|
.regen-btn:hover:not(:disabled) {
|
||||||
border-color: #7c3aed;
|
border-color: #00FFFF;
|
||||||
color: #a78bfa;
|
color: #00FFFF;
|
||||||
}
|
}
|
||||||
.regen-btn:disabled { opacity: .4; cursor: default; }
|
.regen-btn:disabled { opacity: .3; cursor: default; }
|
||||||
|
|
||||||
/* Ошибка */
|
/* Ошибка */
|
||||||
.voice-card-error {
|
.voice-card-error {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #f87171;
|
color: #FF0040;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue