741 lines
38 KiB
TypeScript
741 lines
38 KiB
TypeScript
import React from 'react';
|
||
import type { ReactElement } from 'react';
|
||
import Head from 'next/head';
|
||
import { useRouter } from 'next/router';
|
||
import {
|
||
mdiArrowDown,
|
||
mdiArrowRight,
|
||
mdiCityVariantOutline,
|
||
mdiCompassOutline,
|
||
mdiMapMarker,
|
||
mdiRoadVariant,
|
||
mdiShieldAccount,
|
||
mdiViewDashboardOutline,
|
||
} from '@mdi/js';
|
||
import BaseButton from '../components/BaseButton';
|
||
import BaseIcon from '../components/BaseIcon';
|
||
import LayoutGuest from '../layouts/Guest';
|
||
import { getPageTitle } from '../config';
|
||
|
||
type CityMarker = {
|
||
id: string;
|
||
name: string;
|
||
district: string;
|
||
contractor: string;
|
||
description: string;
|
||
specialization: string;
|
||
top: string;
|
||
left: string;
|
||
popupTop: string;
|
||
popupLeft: string;
|
||
};
|
||
|
||
type RoleCard = {
|
||
id: string;
|
||
title: string;
|
||
caption: string;
|
||
description: string;
|
||
accent: string;
|
||
};
|
||
|
||
type JourneyContext = {
|
||
roleId: string;
|
||
roleTitle: string;
|
||
city: string;
|
||
contractor: string;
|
||
};
|
||
|
||
const journeyStorageKey = 'roadTalentJourney';
|
||
|
||
const palette = {
|
||
navy: '#243B74',
|
||
midnight: '#1A2A52',
|
||
burgundy: '#56061D',
|
||
berry: '#7A2338',
|
||
paper: '#F5F2EC',
|
||
};
|
||
|
||
const cityMarkers: CityMarker[] = [
|
||
{
|
||
id: 'moscow',
|
||
name: 'Москва',
|
||
district: 'Центральный кластер',
|
||
contractor: 'АО «ДорИнфраструктура»',
|
||
description:
|
||
'Координирует набор инженерных команд, стажировки и быстрый вывод специалистов на федеральные дорожные объекты.',
|
||
specialization: 'Цифровое управление проектами и кадровый резерв',
|
||
top: '35%',
|
||
left: '18%',
|
||
popupTop: '18%',
|
||
popupLeft: '23%',
|
||
},
|
||
{
|
||
id: 'saint-petersburg',
|
||
name: 'Санкт-Петербург',
|
||
district: 'Северо-Западный кластер',
|
||
contractor: 'ООО «Балтийские магистрали»',
|
||
description:
|
||
'Развивает дорожное проектирование и практикум для молодых специалистов под задачи северо-западных регионов.',
|
||
specialization: 'Проектирование, BIM и подготовка мастеров участка',
|
||
top: '26%',
|
||
left: '16%',
|
||
popupTop: '8%',
|
||
popupLeft: '21%',
|
||
},
|
||
{
|
||
id: 'kazan',
|
||
name: 'Казань',
|
||
district: 'Приволжский кластер',
|
||
contractor: 'ГК «Трасса Развития»',
|
||
description:
|
||
'Собирает межвузовские команды для пилотных дорожных полигонов, подготовки механиков и специалистов по безопасности движения.',
|
||
specialization: 'Учебные полигоны и отраслевые акселераторы',
|
||
top: '43%',
|
||
left: '29%',
|
||
popupTop: '24%',
|
||
popupLeft: '34%',
|
||
},
|
||
{
|
||
id: 'yekaterinburg',
|
||
name: 'Екатеринбург',
|
||
district: 'Уральский кластер',
|
||
contractor: 'АО «УралДорКадры»',
|
||
description:
|
||
'Формирует региональный кадровый центр по линейному строительству, эксплуатации техники и управлению подрядом.',
|
||
specialization: 'Линейное строительство и эксплуатация дорожной техники',
|
||
top: '45%',
|
||
left: '42%',
|
||
popupTop: '26%',
|
||
popupLeft: '47%',
|
||
},
|
||
{
|
||
id: 'novosibirsk',
|
||
name: 'Новосибирск',
|
||
district: 'Сибирский кластер',
|
||
contractor: 'ООО «СибАвтоДор»',
|
||
description:
|
||
'Запускает маршруты переобучения, отраслевую аналитику и совместные программы для СПО и подрядчиков.',
|
||
specialization: 'Переобучение и аналитика по потребности в кадрах',
|
||
top: '50%',
|
||
left: '58%',
|
||
popupTop: '31%',
|
||
popupLeft: '62%',
|
||
},
|
||
{
|
||
id: 'vladivostok',
|
||
name: 'Владивосток',
|
||
district: 'Дальневосточный кластер',
|
||
contractor: 'ГК «Восточный путь»',
|
||
description:
|
||
'Курирует кадровые потоки для портовых подходов, мостовых объектов и дорожных узлов Дальнего Востока.',
|
||
specialization: 'Мостостроение, портовая логистика и восточные коридоры',
|
||
top: '54%',
|
||
left: '87%',
|
||
popupTop: '35%',
|
||
popupLeft: '69%',
|
||
},
|
||
];
|
||
|
||
const roleCards: RoleCard[] = [
|
||
{
|
||
id: 'school',
|
||
title: 'Школа',
|
||
caption: 'Ранняя профориентация',
|
||
description: 'Маршрут для учеников, наставников и школьных команд, которым нужен вход в отрасль через экскурсии и треки развития.',
|
||
accent: '#243B74',
|
||
},
|
||
{
|
||
id: 'college',
|
||
title: 'СПО и ВО',
|
||
caption: 'Подготовка специалистов',
|
||
description: 'Для колледжей и вузов, которые синхронизируют образовательные программы с реальными дорожными проектами.',
|
||
accent: '#1A2A52',
|
||
},
|
||
{
|
||
id: 'contractor',
|
||
title: 'Подрядные организации',
|
||
caption: 'Быстрый кадровый контур',
|
||
description: 'Для компаний, которым важно увидеть локальный кадровый резерв, потребности по ролям и точки входа в экосистему.',
|
||
accent: '#56061D',
|
||
},
|
||
{
|
||
id: 'government',
|
||
title: 'Государственные институты',
|
||
caption: 'Управление системой',
|
||
description: 'Для операторов отрасли, которые управляют координацией партнёров, показателями и региональными соглашениями.',
|
||
accent: '#7A2338',
|
||
},
|
||
];
|
||
|
||
function getRoleTitle(roleId: string) {
|
||
return roleCards.find((role) => role.id === roleId)?.title || '';
|
||
}
|
||
|
||
function RoleIllustration({ roleId }: { roleId: string }) {
|
||
const stroke = palette.paper;
|
||
|
||
if (roleId === 'school') {
|
||
return (
|
||
<svg viewBox='0 0 160 160' className='h-36 w-36' fill='none' aria-hidden='true'>
|
||
<circle cx='82' cy='38' r='16' stroke={stroke} strokeWidth='4' />
|
||
<path d='M48 78c10-18 21-27 34-27 12 0 22 8 31 24' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M63 86v35m39-35v35' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M35 110h92' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M42 110l20-18m56 18-20-18' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M18 44h32l8 14H26z' stroke={stroke} strokeWidth='4' strokeLinejoin='round' />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
if (roleId === 'college') {
|
||
return (
|
||
<svg viewBox='0 0 160 160' className='h-36 w-36' fill='none' aria-hidden='true'>
|
||
<circle cx='80' cy='34' r='16' stroke={stroke} strokeWidth='4' />
|
||
<path d='M52 80c9-18 19-27 28-27 12 0 22 8 28 22' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M63 82v42m34-42v42' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M80 18l46 18-46 18-46-18 46-18z' stroke={stroke} strokeWidth='4' strokeLinejoin='round' />
|
||
<path d='M124 39v28' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M42 118c12-10 25-15 38-15s26 5 38 15' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
if (roleId === 'contractor') {
|
||
return (
|
||
<svg viewBox='0 0 160 160' className='h-36 w-36' fill='none' aria-hidden='true'>
|
||
<circle cx='80' cy='34' r='16' stroke={stroke} strokeWidth='4' />
|
||
<path d='M56 80c7-19 16-28 24-28 11 0 21 9 27 28' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M62 82v38m34-38v38' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M48 56l32-20 32 20' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M48 122h64' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M36 101h88' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M28 101l8-23h88l8 23' stroke={stroke} strokeWidth='4' strokeLinejoin='round' />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<svg viewBox='0 0 160 160' className='h-36 w-36' fill='none' aria-hidden='true'>
|
||
<circle cx='80' cy='34' r='16' stroke={stroke} strokeWidth='4' />
|
||
<path d='M55 79c8-18 17-27 25-27 11 0 20 8 26 24' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M61 82v40m34-40v40' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M36 114h88' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M80 56v-16' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
<path d='M48 64l32-16 32 16' stroke={stroke} strokeWidth='4' strokeLinejoin='round' />
|
||
<rect x='48' y='64' width='64' height='30' rx='8' stroke={stroke} strokeWidth='4' />
|
||
<path d='M72 79h16' stroke={stroke} strokeWidth='4' strokeLinecap='round' />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
export default function RoadTalentLanding() {
|
||
const router = useRouter();
|
||
const [showIntro, setShowIntro] = React.useState(true);
|
||
const [selectedCity, setSelectedCity] = React.useState<CityMarker | null>(cityMarkers[0]);
|
||
const [selectedRole, setSelectedRole] = React.useState<string>('');
|
||
const [roleStageVisible, setRoleStageVisible] = React.useState(false);
|
||
const [validationMessage, setValidationMessage] = React.useState('');
|
||
|
||
React.useEffect(() => {
|
||
const introTimer = window.setTimeout(() => {
|
||
setShowIntro(false);
|
||
}, 2600);
|
||
|
||
return () => window.clearTimeout(introTimer);
|
||
}, []);
|
||
|
||
const openRoleStage = () => {
|
||
if (!selectedCity) {
|
||
setValidationMessage('Сначала выберите город на карте.');
|
||
return;
|
||
}
|
||
|
||
setValidationMessage('');
|
||
setRoleStageVisible(true);
|
||
window.setTimeout(() => {
|
||
document.getElementById('roles-stage')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}, 120);
|
||
};
|
||
|
||
const continueToLogin = () => {
|
||
if (!selectedCity) {
|
||
setValidationMessage('Сначала выберите город и подрядчика.');
|
||
return;
|
||
}
|
||
|
||
if (!selectedRole) {
|
||
setValidationMessage('Выберите роль, чтобы продолжить ко входу.');
|
||
return;
|
||
}
|
||
|
||
const roleTitle = getRoleTitle(selectedRole);
|
||
const journeyContext: JourneyContext = {
|
||
roleId: selectedRole,
|
||
roleTitle,
|
||
city: selectedCity.name,
|
||
contractor: selectedCity.contractor,
|
||
};
|
||
|
||
localStorage.setItem(journeyStorageKey, JSON.stringify(journeyContext));
|
||
|
||
router.push({
|
||
pathname: '/login',
|
||
query: {
|
||
role: selectedRole,
|
||
city: selectedCity.name,
|
||
contractor: selectedCity.contractor,
|
||
},
|
||
});
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<Head>
|
||
<title>{getPageTitle('Кадровый суверенитет дорожной отрасли')}</title>
|
||
</Head>
|
||
|
||
<div className='relative overflow-hidden bg-[#0d1530] text-[#F5F2EC]'>
|
||
{showIntro && (
|
||
<div className='road-intro-overlay fixed inset-0 z-50 flex items-center justify-center px-6'>
|
||
<button
|
||
type='button'
|
||
onClick={() => setShowIntro(false)}
|
||
className='absolute right-6 top-6 rounded-full border border-white/25 px-4 py-2 text-sm text-white/80 transition hover:border-white/60 hover:text-white focus:outline-none focus:ring focus:ring-white/40'
|
||
>
|
||
Пропустить
|
||
</button>
|
||
<div className='road-intro-panel flex max-w-3xl flex-col items-center text-center'>
|
||
<div className='road-intro-road mb-10 w-full max-w-xl'>
|
||
<span />
|
||
<span />
|
||
<span />
|
||
</div>
|
||
<p className='mb-4 text-sm uppercase tracking-[0.45em] text-white/65'>кадровый суверенитет дорожной отрасли</p>
|
||
<h1 className='road-intro-title max-w-4xl text-4xl font-semibold tracking-tight md:text-6xl'>
|
||
Соединяем города, подрядчиков и роли в одном маршруте входа
|
||
</h1>
|
||
<p className='mt-6 max-w-2xl text-base text-[#F5F2EC]/78 md:text-lg'>
|
||
Интерактивный MVP-портал для выбора города, просмотра подрядчика и быстрого перехода к авторизации по нужной роли.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className='absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(36,59,116,0.46),_transparent_36%),radial-gradient(circle_at_80%_15%,_rgba(122,35,56,0.28),_transparent_30%),linear-gradient(180deg,_#1A2A52_0%,_#0d1530_60%,_#0c1123_100%)]' />
|
||
<div className='absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/40 to-transparent' />
|
||
|
||
<div className='relative z-10 mx-auto flex min-h-screen max-w-7xl flex-col px-6 pb-10 pt-6 lg:px-10'>
|
||
<header className='mb-10 flex flex-col gap-4 rounded-[28px] border border-white/10 bg-white/5 px-5 py-4 backdrop-blur md:flex-row md:items-center md:justify-between'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.4em] text-white/55'>MVP-портал</p>
|
||
<div className='mt-2 flex items-center gap-3'>
|
||
<div className='flex h-11 w-11 items-center justify-center rounded-2xl bg-[#243B74] shadow-[0_0_0_6px_rgba(245,242,236,0.04)]'>
|
||
<BaseIcon path={mdiRoadVariant} size={24} />
|
||
</div>
|
||
<div>
|
||
<p className='text-lg font-semibold md:text-xl'>Кадровый суверенитет</p>
|
||
<p className='text-sm text-white/65'>дорожной отрасли России</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='flex flex-wrap items-center gap-3'>
|
||
<button
|
||
type='button'
|
||
onClick={() => document.getElementById('map-stage')?.scrollIntoView({ behavior: 'smooth', block: 'start' })}
|
||
className='inline-flex items-center gap-2 rounded-full border border-white/15 px-4 py-2 text-sm text-white/85 transition hover:border-white/35 hover:bg-white/8 focus:outline-none focus:ring focus:ring-white/30'
|
||
>
|
||
<BaseIcon path={mdiCompassOutline} size={18} />
|
||
К карте
|
||
</button>
|
||
<BaseButton href='/login' label='Войти' color='info' className='border-0 bg-[#243B74] px-5 text-white hover:bg-[#314c90]' />
|
||
<BaseButton
|
||
href='/login'
|
||
label='Админ-интерфейс'
|
||
color='white'
|
||
outline
|
||
className='border-white/25 bg-transparent px-5 text-white hover:border-white/45 hover:bg-white/10'
|
||
/>
|
||
</div>
|
||
</header>
|
||
|
||
<section className='grid items-start gap-8 pb-10 lg:grid-cols-[1.1fr_0.9fr] lg:pb-16'>
|
||
<div className='max-w-3xl'>
|
||
<div className='mb-5 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/6 px-4 py-2 text-sm text-white/75'>
|
||
<span className='h-2.5 w-2.5 rounded-full bg-[#F5F2EC]' />
|
||
Анимация входа → выбор города → роль → логин
|
||
</div>
|
||
<h1 className='max-w-4xl text-4xl font-semibold leading-tight md:text-6xl'>
|
||
Выберите город на карте России и войдите в отраслевую экосистему с нужной ролью.
|
||
</h1>
|
||
<p className='mt-6 max-w-2xl text-base leading-7 text-white/70 md:text-lg'>
|
||
Первый MVP-срез уже проводит пользователя по ключевому сценарию: показывает городские точки, раскрывает карточку подрядчика,
|
||
предлагает роль и передаёт контекст в авторизацию без лишних шагов.
|
||
</p>
|
||
|
||
<div className='mt-8 flex flex-wrap gap-3'>
|
||
<button
|
||
type='button'
|
||
onClick={() => document.getElementById('map-stage')?.scrollIntoView({ behavior: 'smooth', block: 'start' })}
|
||
className='inline-flex items-center gap-2 rounded-full bg-[#F5F2EC] px-6 py-3 text-sm font-medium text-[#1A2A52] transition hover:translate-y-[-1px] hover:bg-white focus:outline-none focus:ring focus:ring-white/40'
|
||
>
|
||
Начать маршрут
|
||
<BaseIcon path={mdiArrowDown} size={18} />
|
||
</button>
|
||
<BaseButton
|
||
href='/login'
|
||
label='Перейти ко входу'
|
||
color='white'
|
||
outline
|
||
className='border-white/20 bg-transparent px-6 text-white hover:border-white/40 hover:bg-white/10'
|
||
/>
|
||
</div>
|
||
|
||
<div className='mt-10 grid gap-4 sm:grid-cols-3'>
|
||
{[
|
||
{ value: '6', label: 'городов в демо-карте' },
|
||
{ value: '4', label: 'роли входа' },
|
||
{ value: '1', label: 'сквозной маршрут MVP' },
|
||
].map((item) => (
|
||
<div key={item.label} className='rounded-[24px] border border-white/10 bg-white/6 p-5 backdrop-blur'>
|
||
<div className='text-3xl font-semibold text-white'>{item.value}</div>
|
||
<div className='mt-2 text-sm leading-6 text-white/65'>{item.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className='rounded-[32px] border border-white/10 bg-gradient-to-br from-white/12 via-white/8 to-white/5 p-6 shadow-[0_40px_120px_rgba(5,9,20,0.45)] backdrop-blur'>
|
||
<div className='mb-6 flex items-center justify-between'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.32em] text-white/55'>Маршрут пользователя</p>
|
||
<h2 className='mt-2 text-2xl font-semibold'>Первый экран, который продаёт логику сервиса</h2>
|
||
</div>
|
||
<div className='rounded-full border border-white/15 bg-white/8 px-3 py-1 text-xs text-white/70'>public MVP</div>
|
||
</div>
|
||
|
||
<div className='space-y-4'>
|
||
{[
|
||
'1. Анимированный вход знакомит пользователя с сервисом.',
|
||
'2. Карта России позволяет кликнуть по городам и увидеть подрядчика.',
|
||
'3. Выбор роли переносится в экран логина и дальше сохраняется для рабочего кабинета.',
|
||
].map((item) => (
|
||
<div key={item} className='flex items-start gap-3 rounded-[22px] border border-white/8 bg-[#101936]/70 p-4'>
|
||
<div className='mt-1 flex h-8 w-8 items-center justify-center rounded-full bg-[#243B74] text-sm font-semibold'>•</div>
|
||
<p className='text-sm leading-6 text-white/75'>{item}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className='mt-6 rounded-[24px] border border-[#7A2338]/60 bg-[linear-gradient(135deg,_rgba(122,35,56,0.18),_rgba(26,42,82,0.25))] p-5'>
|
||
<p className='text-sm uppercase tracking-[0.28em] text-[#F5F2EC]/55'>что дальше в админке</p>
|
||
<p className='mt-3 text-sm leading-7 text-[#F5F2EC]/82'>
|
||
После входа администратор уже может пользоваться существующими справочниками <span className='font-medium text-white'>Cities</span>,
|
||
<span className='font-medium text-white'> Contractors</span> и <span className='font-medium text-white'>Role cards</span> для наполнения отраслевых данных.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id='map-stage' className='grid gap-6 pb-10 lg:grid-cols-[1.25fr_0.75fr] lg:pb-16'>
|
||
<div className='rounded-[32px] border border-white/10 bg-white/6 p-5 shadow-[0_24px_90px_rgba(4,8,18,0.45)] backdrop-blur md:p-6'>
|
||
<div className='mb-5 flex flex-col gap-4 md:flex-row md:items-center md:justify-between'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.32em] text-white/55'>Шаг 1</p>
|
||
<h2 className='mt-2 text-2xl font-semibold'>Интерактивная карта России по городам</h2>
|
||
</div>
|
||
<div className='rounded-full border border-white/10 bg-[#0f1734] px-4 py-2 text-sm text-white/70'>
|
||
Нажмите на город, чтобы открыть подрядчика
|
||
</div>
|
||
</div>
|
||
|
||
<div className='relative overflow-hidden rounded-[28px] border border-white/10 bg-[radial-gradient(circle_at_20%_20%,_rgba(245,242,236,0.08),_transparent_28%),linear-gradient(180deg,_rgba(15,23,52,0.96),_rgba(14,19,38,0.98))] p-4 md:p-6'>
|
||
<div className='absolute inset-x-0 top-6 flex justify-center'>
|
||
<div className='rounded-full border border-white/10 bg-white/10 px-4 py-2 text-xs uppercase tracking-[0.25em] text-white/55'>Россия · дорожные кластеры</div>
|
||
</div>
|
||
|
||
<div className='relative mt-10 min-h-[450px] overflow-hidden rounded-[24px] border border-white/8 bg-[linear-gradient(180deg,_rgba(255,255,255,0.02),_rgba(255,255,255,0.01))]'>
|
||
<div className='absolute inset-0 bg-[linear-gradient(90deg,_rgba(255,255,255,0.02)_1px,_transparent_1px),linear-gradient(_rgba(255,255,255,0.02)_1px,_transparent_1px)] bg-[size:42px_42px] opacity-30' />
|
||
<svg viewBox='0 0 900 460' className='absolute inset-0 h-full w-full'>
|
||
<defs>
|
||
<linearGradient id='russiaFill' x1='0%' x2='100%' y1='0%' y2='100%'>
|
||
<stop offset='0%' stopColor='rgba(245,242,236,0.16)' />
|
||
<stop offset='100%' stopColor='rgba(245,242,236,0.07)' />
|
||
</linearGradient>
|
||
</defs>
|
||
<path
|
||
d='M63 245l41-61 92-27 91 13 55-32 59 7 48-24 70 16 35-12 65 20 74-3 61 28 25 32-37 23 35 25-14 33-63 10-54 24-90 4-72 29-91 3-86 26-81-14-79 17-64-19 10-34-36-35z'
|
||
fill='url(#russiaFill)'
|
||
stroke='rgba(245,242,236,0.22)'
|
||
strokeWidth='3'
|
||
strokeLinejoin='round'
|
||
/>
|
||
</svg>
|
||
|
||
{selectedCity && (
|
||
<div
|
||
className='absolute hidden w-72 max-w-[80vw] rounded-[22px] border border-white/10 bg-[#f5f2ec] p-4 text-[#1A2A52] shadow-[0_22px_60px_rgba(5,10,28,0.55)] md:block'
|
||
style={{ top: selectedCity.popupTop, left: selectedCity.popupLeft }}
|
||
>
|
||
<p className='text-xs uppercase tracking-[0.28em] text-[#7A2338]'>Подрядчик</p>
|
||
<h3 className='mt-2 text-lg font-semibold'>{selectedCity.contractor}</h3>
|
||
<p className='mt-2 text-sm leading-6 text-[#1A2A52]/82'>{selectedCity.description}</p>
|
||
</div>
|
||
)}
|
||
|
||
{cityMarkers.map((city) => {
|
||
const isActive = selectedCity?.id === city.id;
|
||
|
||
return (
|
||
<button
|
||
key={city.id}
|
||
type='button'
|
||
onClick={() => {
|
||
setSelectedCity(city);
|
||
setValidationMessage('');
|
||
}}
|
||
className='group absolute -translate-x-1/2 -translate-y-1/2 focus:outline-none'
|
||
style={{ top: city.top, left: city.left }}
|
||
aria-label={`Открыть подрядчика для города ${city.name}`}
|
||
>
|
||
<span
|
||
className={`absolute left-1/2 top-1/2 h-14 w-14 -translate-x-1/2 -translate-y-1/2 rounded-full blur-md transition ${
|
||
isActive ? 'bg-[#F5F2EC]/35' : 'bg-[#243B74]/20 group-hover:bg-[#F5F2EC]/20'
|
||
}`}
|
||
/>
|
||
<span
|
||
className={`relative flex h-12 w-12 items-center justify-center rounded-full border transition ${
|
||
isActive
|
||
? 'border-[#F5F2EC] bg-[#F5F2EC] text-[#1A2A52]'
|
||
: 'border-white/25 bg-[#243B74]/85 text-white group-hover:border-white/55 group-hover:bg-[#314c90]'
|
||
}`}
|
||
>
|
||
<BaseIcon path={mdiMapMarker} size={22} />
|
||
</span>
|
||
<span
|
||
className={`mt-2 inline-flex rounded-full px-3 py-1 text-xs font-medium transition ${
|
||
isActive
|
||
? 'bg-[#F5F2EC] text-[#1A2A52]'
|
||
: 'bg-[#101936]/90 text-white/80 group-hover:text-white'
|
||
}`}
|
||
>
|
||
{city.name}
|
||
</span>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className='mt-5 flex flex-wrap gap-2'>
|
||
{cityMarkers.map((city) => (
|
||
<button
|
||
key={city.id}
|
||
type='button'
|
||
onClick={() => {
|
||
setSelectedCity(city);
|
||
setValidationMessage('');
|
||
}}
|
||
className={`rounded-full border px-4 py-2 text-sm transition focus:outline-none focus:ring focus:ring-white/30 ${
|
||
selectedCity?.id === city.id
|
||
? 'border-[#F5F2EC] bg-[#F5F2EC] text-[#1A2A52]'
|
||
: 'border-white/12 bg-white/5 text-white/75 hover:border-white/30 hover:text-white'
|
||
}`}
|
||
>
|
||
{city.name}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<aside className='rounded-[32px] border border-white/10 bg-[#f5f2ec] p-6 text-[#1A2A52] shadow-[0_24px_90px_rgba(4,8,18,0.35)]'>
|
||
<div className='mb-4 flex items-center justify-between'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.3em] text-[#7A2338]'>Выбранная точка</p>
|
||
<h2 className='mt-2 text-2xl font-semibold'>Карточка подрядчика</h2>
|
||
</div>
|
||
<div className='rounded-2xl bg-[#1A2A52] p-3 text-white'>
|
||
<BaseIcon path={mdiCityVariantOutline} size={22} />
|
||
</div>
|
||
</div>
|
||
|
||
{selectedCity ? (
|
||
<>
|
||
<div className='rounded-[26px] border border-[#1A2A52]/8 bg-white p-5 shadow-[0_16px_40px_rgba(26,42,82,0.08)]'>
|
||
<p className='text-sm uppercase tracking-[0.25em] text-[#7A2338]'>{selectedCity.district}</p>
|
||
<h3 className='mt-2 text-3xl font-semibold'>{selectedCity.name}</h3>
|
||
<p className='mt-2 text-base font-medium text-[#243B74]'>{selectedCity.contractor}</p>
|
||
<p className='mt-4 text-sm leading-7 text-[#1A2A52]/78'>{selectedCity.description}</p>
|
||
|
||
<div className='mt-5 rounded-[22px] bg-[#F5F2EC] p-4'>
|
||
<p className='text-xs uppercase tracking-[0.24em] text-[#1A2A52]/55'>Фокус подрядчика</p>
|
||
<p className='mt-2 text-sm leading-6 text-[#1A2A52]/82'>{selectedCity.specialization}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='mt-5 grid grid-cols-2 gap-3'>
|
||
{[
|
||
{ label: 'Город', value: selectedCity.name },
|
||
{ label: 'Формат', value: 'Открытый вход' },
|
||
{ label: 'Этап', value: 'Выбор роли' },
|
||
{ label: 'Доступ', value: 'Публичный MVP' },
|
||
].map((item) => (
|
||
<div key={item.label} className='rounded-[22px] bg-[#1A2A52] p-4 text-[#F5F2EC]'>
|
||
<p className='text-xs uppercase tracking-[0.22em] text-white/45'>{item.label}</p>
|
||
<p className='mt-2 text-sm font-medium'>{item.value}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div className='rounded-[24px] border border-dashed border-[#1A2A52]/18 bg-white p-6 text-center'>
|
||
<p className='text-lg font-semibold'>Пока город не выбран</p>
|
||
<p className='mt-2 text-sm leading-6 text-[#1A2A52]/72'>Нажмите на маркер на карте России, чтобы открыть карточку подрядчика и продолжить маршрут.</p>
|
||
</div>
|
||
)}
|
||
|
||
<div className='mt-6 rounded-[24px] bg-[linear-gradient(135deg,_#243B74,_#1A2A52)] p-5 text-[#F5F2EC]'>
|
||
<p className='text-xs uppercase tracking-[0.28em] text-white/55'>Шаг 2</p>
|
||
<h3 className='mt-2 text-xl font-semibold'>Перейти к выбору роли</h3>
|
||
<p className='mt-3 text-sm leading-6 text-white/70'>Продолжите сценарий после выбора города: откроем четыре роли и передадим выбор в экран логина.</p>
|
||
<button
|
||
type='button'
|
||
onClick={openRoleStage}
|
||
className='mt-5 inline-flex items-center gap-2 rounded-full bg-[#F5F2EC] px-5 py-3 text-sm font-medium text-[#1A2A52] transition hover:translate-y-[-1px] hover:bg-white focus:outline-none focus:ring focus:ring-white/40'
|
||
>
|
||
Далее
|
||
<BaseIcon path={mdiArrowRight} size={18} />
|
||
</button>
|
||
{validationMessage && <p className='mt-3 text-sm text-[#F5F2EC]/82'>{validationMessage}</p>}
|
||
</div>
|
||
</aside>
|
||
</section>
|
||
|
||
<section id='roles-stage' className={`pb-14 transition duration-500 ${roleStageVisible ? 'opacity-100' : 'opacity-90'}`}>
|
||
<div className='mb-6 flex flex-col gap-3 md:flex-row md:items-end md:justify-between'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.32em] text-white/55'>Шаг 2</p>
|
||
<h2 className='mt-2 text-3xl font-semibold'>Выберите роль входа</h2>
|
||
<p className='mt-3 max-w-2xl text-sm leading-7 text-white/70'>Четыре карточки роли отражают ключевых участников экосистемы: от школы до подрядных организаций и государственных институтов.</p>
|
||
</div>
|
||
<div className='rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm text-white/70'>
|
||
{selectedCity ? `Город: ${selectedCity.name}` : 'Сначала выберите город'}
|
||
</div>
|
||
</div>
|
||
|
||
<div className='grid gap-5 lg:grid-cols-4'>
|
||
{roleCards.map((role) => {
|
||
const isActive = selectedRole === role.id;
|
||
|
||
return (
|
||
<button
|
||
key={role.id}
|
||
type='button'
|
||
onClick={() => {
|
||
setSelectedRole(role.id);
|
||
setValidationMessage('');
|
||
}}
|
||
className={`group relative overflow-hidden rounded-[30px] border p-6 text-left transition duration-200 focus:outline-none focus:ring focus:ring-white/35 ${
|
||
isActive
|
||
? 'border-white/50 bg-[linear-gradient(180deg,_rgba(245,242,236,0.16),_rgba(255,255,255,0.06))] shadow-[0_28px_70px_rgba(5,9,20,0.42)]'
|
||
: 'border-white/10 bg-white/5 hover:border-white/25 hover:bg-white/8'
|
||
}`}
|
||
>
|
||
<div
|
||
className='absolute inset-x-0 top-0 h-1'
|
||
style={{ background: `linear-gradient(90deg, ${role.accent}, rgba(245,242,236,0.75))` }}
|
||
/>
|
||
<div className='flex items-center justify-between'>
|
||
<p className='text-xs uppercase tracking-[0.28em] text-white/55'>{role.caption}</p>
|
||
<span className={`rounded-full px-3 py-1 text-xs ${isActive ? 'bg-[#F5F2EC] text-[#1A2A52]' : 'bg-white/10 text-white/65'}`}>
|
||
{isActive ? 'Выбрано' : 'Роль'}
|
||
</span>
|
||
</div>
|
||
<div className='mt-4 flex justify-center rounded-[26px] bg-[linear-gradient(180deg,_rgba(245,242,236,0.05),_rgba(245,242,236,0.01))] p-4'>
|
||
<RoleIllustration roleId={role.id} />
|
||
</div>
|
||
<h3 className='mt-5 text-2xl font-semibold'>{role.title}</h3>
|
||
<p className='mt-3 text-sm leading-7 text-white/70'>{role.description}</p>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className='mt-8 grid gap-6 rounded-[32px] border border-white/10 bg-white/5 p-6 backdrop-blur lg:grid-cols-[0.95fr_1.05fr]'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.32em] text-white/55'>Шаг 3</p>
|
||
<h3 className='mt-2 text-2xl font-semibold'>Переход к логину и паролю</h3>
|
||
<p className='mt-3 text-sm leading-7 text-white/70'>Выбранные город, подрядчик и роль будут показаны на экране логина и сохранятся для первого экрана внутри кабинета.</p>
|
||
</div>
|
||
<div className='grid gap-4 rounded-[26px] bg-[#f5f2ec] p-5 text-[#1A2A52] md:grid-cols-3'>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.25em] text-[#1A2A52]/50'>Город</p>
|
||
<p className='mt-2 text-base font-semibold'>{selectedCity?.name || 'Не выбран'}</p>
|
||
</div>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.25em] text-[#1A2A52]/50'>Подрядчик</p>
|
||
<p className='mt-2 text-base font-semibold'>{selectedCity?.contractor || 'Ожидание выбора'}</p>
|
||
</div>
|
||
<div>
|
||
<p className='text-xs uppercase tracking-[0.25em] text-[#1A2A52]/50'>Роль</p>
|
||
<p className='mt-2 text-base font-semibold'>{getRoleTitle(selectedRole) || 'Не выбрана'}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='mt-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between'>
|
||
<p className='text-sm text-white/70'>
|
||
{selectedRole
|
||
? `Готово: выбран маршрут «${selectedCity?.name || ''} → ${getRoleTitle(selectedRole)}».`
|
||
: 'Выберите одну из ролей, чтобы открыть следующий шаг.'}
|
||
</p>
|
||
<div className='flex flex-wrap gap-3'>
|
||
<BaseButton
|
||
href='/login'
|
||
label='Пропустить к логину'
|
||
color='white'
|
||
outline
|
||
className='border-white/20 bg-transparent px-5 text-white hover:border-white/40 hover:bg-white/10'
|
||
/>
|
||
<button
|
||
type='button'
|
||
onClick={continueToLogin}
|
||
className='inline-flex items-center gap-2 rounded-full bg-[#F5F2EC] px-6 py-3 text-sm font-medium text-[#1A2A52] transition hover:translate-y-[-1px] hover:bg-white focus:outline-none focus:ring focus:ring-white/35'
|
||
>
|
||
Продолжить к логину
|
||
<BaseIcon path={mdiShieldAccount} size={18} />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<footer className='mt-auto flex flex-col gap-4 border-t border-white/10 pt-6 text-sm text-white/60 md:flex-row md:items-center md:justify-between'>
|
||
<p>© 2026 Кадровый суверенитет дорожной отрасли — первый публичный маршрут входа для партнёров и образовательных учреждений.</p>
|
||
<div className='flex flex-wrap items-center gap-3'>
|
||
<BaseButton
|
||
href='/login'
|
||
label='Открыть кабинет'
|
||
color='white'
|
||
outline
|
||
className='border-white/20 bg-transparent text-white hover:border-white/40 hover:bg-white/10'
|
||
/>
|
||
<BaseButton
|
||
href='/dashboard'
|
||
label='После входа: Overview'
|
||
color='white'
|
||
outline
|
||
className='border-white/20 bg-transparent text-white hover:border-white/40 hover:bg-white/10'
|
||
icon={mdiViewDashboardOutline}
|
||
/>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|
||
|
||
RoadTalentLanding.getLayout = function getLayout(page: ReactElement) {
|
||
return <LayoutGuest>{page}</LayoutGuest>;
|
||
};
|