fixes and updates
This commit is contained in:
+18
-4
@@ -3,11 +3,25 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="keywords" content="интерактивные,тренажеры,симуляторы,эффективность,VR,промышленность,обучение,образование">
|
||||
<meta
|
||||
name="keywords"
|
||||
content="интерактивные,тренажеры,симуляторы,эффективность,VR,промышленность,обучение,образование"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta property="og:title" content="Интерактивные тренажеры для промышленности и образования" />
|
||||
<meta property="og:description" content="Создаем интерактивные тренажеры для промышленности и образования. Помогаем сократить затраты на обучение, повысить безопасность и производительность"/>
|
||||
<meta property="og:url" content="https://graff.training"/>
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Интерактивные тренажеры для промышленности и образования"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Создаем интерактивные тренажеры для промышленности и образования. Помогаем сократить затраты на обучение, повысить безопасность и производительность"
|
||||
/>
|
||||
<meta property="og:url" content="https://graff.training" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://graff.training/src/assets/decreasing/effect.jpg"
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<title>Интерактивные тренажеры для промышленности и образования</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+3
-2
@@ -10,16 +10,17 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"countries-phone-masks": "^1.1.0",
|
||||
"framer-motion": "^11.2.14",
|
||||
"ky": "^1.4.0",
|
||||
"libphonenumber-js": "^1.11.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-phone-number-input": "^3.4.5",
|
||||
"react-rangeslider": "^2.2.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-router-hash-link": "^2.4.3",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"react-usestateref": "^1.0.9",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zustand": "^4.5.4"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ export function Footer() {
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<Link
|
||||
to="https://graff.tech/privacypolicy"
|
||||
className="sm:font-medium flex gap-4 m-text outline-none"
|
||||
className="flex gap-4 outline-none sm:font-medium m-text"
|
||||
>
|
||||
Политика конфиденциальности <span>graff.tech</span>
|
||||
</Link>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { PropsWithChildren, useRef, useState } from 'react';
|
||||
import { AnchorLink } from '../../ui/AnchorLink';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Lang, useLanguageStore } from '../../store/languageStore';
|
||||
import { HashLink } from 'react-router-hash-link';
|
||||
import { useHover, useOnClickOutside } from 'usehooks-ts';
|
||||
import { useModalStore } from '../../store/modalStore';
|
||||
import { ModalWithForm } from '../Main/ModalWithForm';
|
||||
@@ -37,14 +36,14 @@ export function Header() {
|
||||
<LogoIcon className="lg:hidden" />
|
||||
{width >= 1024 && <LogoWithTextIcon />}
|
||||
</Link>
|
||||
<div className="flex">
|
||||
<div className="flex mx-auto">
|
||||
<AnchorLink route="#products">Типы тренажеров</AnchorLink>
|
||||
<AnchorLink route="#trainings">Варианты комплектации</AnchorLink>
|
||||
<AnchorLink route="#projects">Проекты</AnchorLink>
|
||||
<AnchorLink route="#events">События</AnchorLink>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setModal(<ModalWithForm />)}
|
||||
className="rounded-none btn-text font-semibold max-sm:hidden px-10"
|
||||
className="px-10 font-semibold rounded-none btn-text max-sm:hidden"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
@@ -52,11 +51,10 @@ export function Header() {
|
||||
<button
|
||||
ref={menuBtnRef}
|
||||
onClick={() => setMenuOpen(prev => !prev)}
|
||||
className="px-6 py-5 min-[1350px]:hidden border-[#3D425C] max-sm:border-l outline-none"
|
||||
className="px-6 py-5 xl:hidden border-[#3D425C] max-sm:border-l outline-none"
|
||||
>
|
||||
{!menuOpen ? <BurgerIcon /> : <CloseIcon />}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, visibility: 'hidden' }}
|
||||
@@ -76,7 +74,6 @@ export function Header() {
|
||||
<BurgerAnchor route="#products">Типы тренажеров</BurgerAnchor>
|
||||
<BurgerAnchor route="#trainings">Варианты комплектации</BurgerAnchor>
|
||||
<BurgerAnchor route="#projects">Проекты</BurgerAnchor>
|
||||
<BurgerAnchor route="#events">События</BurgerAnchor>
|
||||
</div>
|
||||
<div className="grid grid-cols-[2fr_1fr_1fr] sm:grid-cols-2">
|
||||
<Button
|
||||
@@ -85,7 +82,7 @@ export function Header() {
|
||||
setModal(<ModalWithForm />);
|
||||
}}
|
||||
width="full"
|
||||
className="sm:hidden font-semibold btn-text rounded-none outline-none"
|
||||
className="font-semibold rounded-none outline-none sm:hidden btn-text"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
@@ -102,13 +99,13 @@ function BurgerAnchor({
|
||||
route,
|
||||
}: PropsWithChildren<{ route: string }>) {
|
||||
return (
|
||||
<HashLink
|
||||
<Link
|
||||
to={route}
|
||||
className="flex items-center px-10 py-6 gap-1 btn-text bg-[#14161F] w-full last:border-b-0 [&:not(:last-child)]:border-b sm:border-l font-semibold border-[#3D425C] lg:hover:bg-[#3D425C] active:bg-[#14161F]"
|
||||
>
|
||||
<CubeIcon />
|
||||
{children}
|
||||
</HashLink>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,28 +135,32 @@ function LangToggler({ lang }: { lang: Lang }) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="max-w-[101px] max-[1349px]:hidden"
|
||||
className="max-w-[101px]s max-xl:hidden relative"
|
||||
style={{ backgroundColor: hovered ? '#3D425C' : 'transparent' }}
|
||||
>
|
||||
<button
|
||||
ref={langTogglerRef}
|
||||
onClick={() => setOpen(prev => !prev)}
|
||||
className="mx-6 h-full gap-x-1 items-center flex font-semibold btn-text outline-none"
|
||||
className="flex items-center h-full mx-6 font-semibold outline-none gap-x-1 btn-text"
|
||||
>
|
||||
{lang}
|
||||
<ChevronDownIcon />
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
className="absolute z-20 grid grid-cols-2 min-w-[101px]"
|
||||
className="absolute z-20 grid grid-cols-2 min-w-[101px]s w-full"
|
||||
onClick={() => setOpen(false)}
|
||||
initial={{ visibility: 'hidden' }}
|
||||
initial={{ visibility: 'hidden', opacity: 0 }}
|
||||
animate={{
|
||||
visibility: open ? 'visible' : 'hidden',
|
||||
opacity: +open,
|
||||
}}
|
||||
exit={{ visibility: 'hidden', opacity: 0 }}
|
||||
>
|
||||
<ChooseLang currentLang={'RU'} />
|
||||
<ChooseLang currentLang={'EN'} />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import { Footer } from './Footer';
|
||||
import { ModalContainer } from '../Main/ModalContainer';
|
||||
import { Feedback } from '../Main/Contacts';
|
||||
import { Clients } from '../Main/Clients';
|
||||
import { ScrollToHashElement } from './ScrollToHashElement';
|
||||
|
||||
export function Layout() {
|
||||
return (
|
||||
<>
|
||||
<ScrollToHashElement />
|
||||
<Header />
|
||||
<main className="relative overflow-x-clip lg:px-10 sm:px-6 px-4">
|
||||
<main className="relative px-4 overflow-clip lg:px-10 sm:px-6">
|
||||
<Outlet />
|
||||
<Clients />
|
||||
<Feedback />
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export function ScrollToHashElement() {
|
||||
const { hash } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const hashElement = document.getElementById(hash.slice(1));
|
||||
|
||||
if (hashElement)
|
||||
hashElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'nearest',
|
||||
block: 'start',
|
||||
});
|
||||
}, [hash]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -7,13 +7,14 @@ import { getIcon } from '../../utils/getIcon';
|
||||
|
||||
export function Availables() {
|
||||
return (
|
||||
<div className="lg:py-[100px] py-14 sm:grid lg:grid-cols-12 lg:grid-rows-[repeat(4,fit)] max-lg:grid-cols-8">
|
||||
<Title className="lg:mb-14 mb-6 col-span-full">
|
||||
<div className="lg:py-[100px] py-14 sm:grid lg:grid-cols-12 sm:grid-rows-[repeat(4,fit)] sm:grid-cols-3">
|
||||
<Title className="row-start-1 mb-6 lg:mb-14 col-span-full">
|
||||
<span className="text-gradient text-wrap">Многопользовательский </span>
|
||||
<br className="lg:hidden" />
|
||||
режим обучения
|
||||
</Title>
|
||||
<div className="col-span-6 col-start-1 row-span-2 row-start-2 bg-[url(src/assets/availables/image.png)] bg-cover bg-no-repeat bg-center" />
|
||||
<div className="lg:col-span-6 row-span-2 grid grid-cols-2 grid-rows-2 lg:-mr-10 sm:-mr-6 slg:aspect-[808/560]">
|
||||
<div className="max-lg:hidden lg:col-span-6 col-start-1 lg:row-span-2 sm:col-span-3 lg:row-start-2 sm:row-start-3 sm:row-span-1 bg-[url(src/assets/availables/image.png)] bg-cover bg-no-repeat bg-center" />
|
||||
<div className="grid max-lg:col-span-full sm:max-lg:row-start-2 lg:col-span-6 lg:row-span-2 lg:grid-cols-2 sm:grid-cols-3 lg:grid-rows-2 lg:-mr-10 max-lg:-mx-6">
|
||||
<MultiUserFeature
|
||||
type="processes"
|
||||
text="отработка производственных процессов, в которых участвует группа людей"
|
||||
@@ -27,8 +28,9 @@ export function Availables() {
|
||||
text="координация действий между несколькими сотрудниками"
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:hidden lg:col-span-6 col-start-1 lg:row-span-2 sm:col-span-3 lg:row-start-2 sm:row-start-3 sm:row-span-1 bg-[url(src/assets/availables/image.png)] bg-cover bg-no-repeat bg-center sm:aspect-[728/356] aspect-[3/2] max-sm:-mx-6" />
|
||||
<AppearanceText
|
||||
className="col-start-1 lg:col-span-6 col-span-7 mt-8 max-lg:mt-6"
|
||||
className="col-span-7 col-start-1 mt-8 lg:col-span-6 max-lg:mt-6"
|
||||
splits={[
|
||||
'В одном ',
|
||||
'цифровом ',
|
||||
@@ -56,18 +58,20 @@ function MultiUserFeature({
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const hovered = useHover(ref);
|
||||
const isInView = useInView(ref, {
|
||||
margin: `0px 0px -${window.innerHeight - (ref.current?.clientHeight ?? 0)}px`,
|
||||
margin: `0px 0px ${(ref.current?.clientHeight ?? 0) - window.innerHeight}px`,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={
|
||||
'bg-right-bottom bg-no-repeat flex flex-col bg-[url(src/assets/availables/highlight.png)] bg-[length:0%_0%] hover:bg-[length:100%_100%] transition-all min-h-[280px] duration-300 w-full justify-between items-start px-10 py-6 lg:border-t lg:first:border-r lg:last:border-b border-[#3D425C] lg:col-span-1 lg:last:col-span-2 lg:aspect-[403/280]lg:last:aspect-[808/280]'
|
||||
}
|
||||
className="max-sm:border-t max-sm:last:border-b sm:max-lg:border-y max-h-60 sm:max-lg:[&:not(:last-child)]:border-r bg-right-bottom bg-no-repeat flex flex-col bg-[url(src/assets/availables/highlight.png)] bg-[length:0%_0%] hover:bg-[length:100%_100%] transition-all min-h-[280px] duration-300 w-full justify-between items-start px-10 py-6 lg:border-t lg:first:border-r lg:last:border-b border-[#3D425C] col-span-1 lg:last:col-span-2"
|
||||
>
|
||||
{getIcon(type, hovered, 'mb-4 max-sm:hidden w-14 h-14')}
|
||||
{getIcon(type, isInView, 'mb-4 sm:hidden w-14 h-14')}
|
||||
<div className="max-lg:hidden">
|
||||
{getIcon(type, hovered, 'mb-4 max-lg:hidden w-14 h-14')}
|
||||
</div>
|
||||
<div className="lg:hidden">
|
||||
{getIcon(type, isInView, 'mb-4 lg:hidden w-14 h-14')}
|
||||
</div>
|
||||
<p className="l-text">{text}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,12 @@ import { clients } from '../../consts/clients';
|
||||
|
||||
export function Clients() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center overflow-hidden w-screen sm:-mx-6 mt-10 min-h-[117px]">
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center overflow-hidden w-screen lg:-mx-10 -mx-6 mt-10 min-h-[117px]">
|
||||
<MarqueeHalf items={clients.slice(0, clients.length / 3)} />
|
||||
<MarqueeHalf items={clients.slice(0, clients.length / 3)} />
|
||||
</div>
|
||||
<div className="flex items-center overflow-hidden w-screen sm:-mx-6 min-h-[117px]">
|
||||
<div className="flex items-center overflow-hidden w-screen lg:-mx-10 -mx-6 min-h-[117px]">
|
||||
<MarqueeHalf
|
||||
reversed
|
||||
items={clients.slice(clients.length / 3, (2 * clients.length) / 3)}
|
||||
@@ -17,7 +17,7 @@ export function Clients() {
|
||||
items={clients.slice(clients.length / 3, (2 * clients.length) / 3)}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-b border-[#3D425C] flex items-center overflow-hidden w-screen sm:-mx-6 min-h-[117px] pb-16">
|
||||
<div className="border-b border-[#3D425C] flex items-center overflow-hidden w-screen lg:-mx-10 -mx-6 min-h-[117px] pb-16">
|
||||
<MarqueeHalf items={clients.slice(2 * (clients.length / 3))} />
|
||||
<MarqueeHalf items={clients.slice(2 * (clients.length / 3))} />
|
||||
</div>
|
||||
@@ -35,17 +35,21 @@ function MarqueeHalf({
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'flex flex-nowrap overflow-clip items-center py-2 [flex:0_0_auto] animate-infinite-scroll ' +
|
||||
(reversed ? '[animation-direction:reverse]' : '')
|
||||
'flex flex-nowrap ' +
|
||||
(reversed
|
||||
? '[animation:infinite-scroll_45s_linear_infinite_reverse]'
|
||||
: 'animate-infinite-scroll')
|
||||
}
|
||||
>
|
||||
{items.map(client => (
|
||||
<div className="border-l border-[#3D425C] w-[312px] h-[124px] flex justify-center items-center relative">
|
||||
<img
|
||||
key={client.src}
|
||||
src={client.src}
|
||||
alt={client.src}
|
||||
className="max-w-full h-auto !relative object-covesr mx-12"
|
||||
className="!relative"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { YouTubeIcon } from '../icons/YoutubeIcon';
|
||||
export function Feedback() {
|
||||
return (
|
||||
<div className="sm:grid lg:grid-cols-12 sm:grid-cols-2 lg:grid-rows-[repeat(min-content,2)] sm:grid-rows-[repeat(min-content,3)] lg:gap-x-4 sm:gap-x-14 lg:gap-y-[68px] pb-20 pt-[70px]">
|
||||
<h2 className="lg:col-span-7 sm:col-span-full h2 font-medium max-lg:mb-6">
|
||||
<h2 className="font-medium lg:col-span-7 sm:col-span-full h2 max-lg:mb-6">
|
||||
Хотите использовать интерактивные тренажеры в обучении?
|
||||
<br />
|
||||
<span className="text-gradient">Давайте обсудим детали.</span>
|
||||
@@ -18,13 +18,13 @@ export function Feedback() {
|
||||
<Button
|
||||
color="primary"
|
||||
icon={<SendIcon />}
|
||||
className="lg:col-span-3 row-start-2 self-end px-6 py-4 sm:max-lg:mb-20 max-sm:mb-14"
|
||||
className="self-end row-start-2 px-6 py-4 lg:col-span-3 sm:max-lg:mb-20 max-sm:mb-14"
|
||||
width="full"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
<div className="space-y-3 lg:col-start-9 lg:col-span-4 sm:col-span-1 sm:col-start-1 max-sm:mb-8">
|
||||
<h4 className="h4 font-medium mb-1">Свяжитесь с нами</h4>
|
||||
<h4 className="mb-1 text-xl font-medium">Свяжитесь с нами</h4>
|
||||
<Button
|
||||
color="secondary"
|
||||
className="py-4"
|
||||
@@ -42,8 +42,8 @@ export function Feedback() {
|
||||
<span className="btn-text opacity-80">Позвонить</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-4 lg:col-start-9 lg:col-span-4 col-start-2 lg:row-start-2 sm:row-start-3 lg:self-end sm:flex sm:flex-col sm:justify-between">
|
||||
<h4 className="h4 font-medium">Социальные сети</h4>
|
||||
<div className="col-start-2 space-y-4 lg:col-start-9 lg:col-span-4 lg:row-start-2 sm:row-start-3 lg:self-end sm:flex sm:flex-col sm:justify-between">
|
||||
<h4 className="font-medium h4">Социальные сети</h4>
|
||||
<div className="flex gap-x-2">
|
||||
<Link
|
||||
to={'https://www.youtube.com/@GRAFFtech'}
|
||||
|
||||
@@ -40,7 +40,7 @@ function DecreasingOption({ text, number }: { text: string; number: number }) {
|
||||
return (
|
||||
<div className="group">
|
||||
<AppearanceHr delay={number * 0.5} />
|
||||
<div className="flex justify-between items-center py-5 gap-x-4">
|
||||
<div className="flex items-center justify-between py-5 gap-x-4">
|
||||
<Plus text={text} />
|
||||
<Number number={number} />
|
||||
</div>
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Title } from '../../ui/Title';
|
||||
export function Distance() {
|
||||
return (
|
||||
<div className="lg:py-[100px] py-14">
|
||||
<Title className="lg:mb-14 mb-6">
|
||||
<Title className="mb-6 lg:mb-14">
|
||||
Платформа GRAFF.training поволяет осуществлять
|
||||
<span className="text-gradient"> дистанционное обучение</span> с любого
|
||||
устройства
|
||||
</Title>
|
||||
<div className="sm:grid lg:grid-cols-12 gap-x-4 gap-y-6">
|
||||
<p className="row-start-1 lg:col-start-1 col-span-6 max-sm:mb-6 l-text">
|
||||
<p className="col-span-6 row-start-1 lg:col-start-1 max-sm:mb-6 l-text">
|
||||
Обучающиеся будут получать доступ к системе с высоко детализированной
|
||||
3D графикой для прохождения сценариев на любом устройстве, без
|
||||
необходимости установки дополнительного ПО.
|
||||
@@ -17,12 +17,12 @@ export function Distance() {
|
||||
<img
|
||||
src="src/assets/distance/datamining_2.jpg"
|
||||
alt="дистанционное обучение с любого устройства"
|
||||
className="row-start-2 lg:col-start-1 col-span-6 max-sm:mb-4 rounded-2xl self-stretch object-cover"
|
||||
className="self-stretch object-cover col-span-6 row-start-2 lg:col-start-1 max-sm:mb-4 rounded-2xl"
|
||||
/>
|
||||
<img
|
||||
src="src/assets/distance/datamining_1.jpg"
|
||||
alt="дистанционное обучение с любого устройства"
|
||||
className="row-start-2 lg:col-start-7 lg:col-span-6 col-span-3 rounded-2xl h-full self-stretch object-cover"
|
||||
className="self-stretch object-cover h-full col-span-3 row-start-2 lg:col-start-7 lg:col-span-6 rounded-2xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,17 +84,17 @@ function Figure({
|
||||
<motion.div
|
||||
ref={root}
|
||||
initial={{
|
||||
background: `bottom right / 50% url(src/assets/efficiency/${type}.png) no-repeat, bottom right / 0% url(src/assets/efficiency/efficiency_backlight.svg) no-repeat`,
|
||||
background: `bottom right / 45% url(src/assets/efficiency/${type}.png) no-repeat, bottom right / 0% url(src/assets/efficiency/efficiency_backlight.svg) no-repeat`,
|
||||
}}
|
||||
whileHover={{
|
||||
backgroundSize: '50%,100% 100%',
|
||||
transition: { duration: 0.075 },
|
||||
backgroundSize: '45%,100%',
|
||||
transition: { duration: 0.125 },
|
||||
}}
|
||||
className="flex px-10 w-full pt-6 bg-no-repeat max-sm:aspect-[3/2] max-sm:border-t max-sm:last:border-b lg:aspect-[532.67/360] relative lg:border-y sm:max-lg:border-t sm:max-lg:last:border-b lg:border-r sm:first:border-r last:border-r-0 border-[#3D425C] sm:max-lg:last:col-span-2 sm:max-lg:row-start-1 sm:max-lg:last:row-start-2"
|
||||
className="flex lg:px-10 px-6 w-full pt-6 bg-no-repeat min-h-[240px] max-sm:border-t max-sm:last:border-b lg:aspect-[532.67/360] relative lg:border-y sm:max-lg:border-t sm:max-lg:last:border-b lg:border-r sm:first:border-r last:border-r-0 border-[#3D425C] sm:max-lg:last:col-span-2 sm:max-lg:row-start-1 sm:max-lg:last:row-start-2"
|
||||
>
|
||||
<div className="flex flex-col justify-between pb-6 max-sm:max-w-[50vw]">
|
||||
<h3 className="lg:font-medium l-text 2xl:max-w-[70%]">{title}</h3>
|
||||
<h1 className="font-medium flex items-center md:text-[clamp(64px,64px+(100vw-768px)/832*32,96px)] md:leading-[clamp(57.6px,57.6px+(100vw-768px)/832*28.8,86.4px)] text-[64px] leading-[57.6px]">
|
||||
<h1 className="font-medium flex lg:items-center items-end md:text-[clamp(64px,64px+(100vw-768px)/832*32,96px)] md:leading-[clamp(57.6px,57.6px+(100vw-768px)/832*28.8,86.4px)] text-[64px] leading-[57.6px]">
|
||||
<span ref={figureRef}>{percents}</span>
|
||||
<span className="md:text-[clamp(32px,32px+(100vw-768px)/832*32,64px)] md:leading-[clamp(32px,32px+(100vw-768px)/832*25.6,57.6px)] text-[32px] leading-8">
|
||||
%
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export function Ellipse() {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -14,7 +14,7 @@ export function Ellipse() {
|
||||
e.clientY - (e.currentTarget as HTMLElement).getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
const animate = useCallback(() => {
|
||||
if (ref.current) {
|
||||
setMousePos(([prevX, prevY]) => [
|
||||
prevX + (x.current - prevX) / 15,
|
||||
@@ -22,7 +22,7 @@ export function Ellipse() {
|
||||
]);
|
||||
}
|
||||
requestRef.current = requestAnimationFrame(animate);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.body?.addEventListener('mousemove', handleMouseMove);
|
||||
@@ -32,7 +32,7 @@ export function Ellipse() {
|
||||
cancelAnimationFrame(requestRef.current!);
|
||||
document.body?.removeEventListener('mousemove', handleMouseMove);
|
||||
};
|
||||
}, []);
|
||||
}, [animate]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -10,11 +10,11 @@ export function Events() {
|
||||
return (
|
||||
<div className="lg:py-[70px] sm:py-14" id="events">
|
||||
<AppearanceHr className="max-lg:hidden" />
|
||||
<div className="pt-5 gap-x-4 w-full sm:grid lg:grid-cols-12 grid-cols-8">
|
||||
<MiniTitle text="события" className="max-sm:mb-2 col-span-2" />
|
||||
<div className="w-full grid-cols-8 pt-5 gap-x-4 sm:grid lg:grid-cols-12">
|
||||
<MiniTitle text="события" className="col-span-2 max-sm:mb-2" />
|
||||
<div className="lg:col-span-9 col-span-full">
|
||||
<AppearanceHr className="sm:hidden" delay={0.5} />
|
||||
<div className="lg:py-7 sm:py-6 py-5 flex justify-between max-sm:flex-col items-start gap-x-4">
|
||||
<div className="flex items-start justify-between py-5 lg:py-7 sm:py-6 max-sm:flex-col gap-x-4">
|
||||
<div className="w-fit">
|
||||
<EventTitle className="sm:mb-8 w-fit">
|
||||
Макет кабины машиниста «Иволга» на выставке ВДНХ
|
||||
@@ -35,12 +35,12 @@ export function Events() {
|
||||
<LinkButton href="/" />
|
||||
</div>
|
||||
<AppearanceHr delay={1} />
|
||||
<div className="py-7 flex max-sm:flex-col sm:items-center justify-between gap-x-4">
|
||||
<div className="flex justify-between py-7 max-sm:flex-col sm:items-center gap-x-4">
|
||||
<EventTitle>Победа на BuildUP 2023 в номинации IT</EventTitle>
|
||||
<LinkButton href="/" />
|
||||
</div>
|
||||
<AppearanceHr delay={1.5} />
|
||||
<div className="py-7 flex max-sm:flex-col sm:items-center justify-between gap-x-4">
|
||||
<div className="flex justify-between py-7 max-sm:flex-col sm:items-center gap-x-4">
|
||||
<EventTitle>
|
||||
Транспортное и специальное тренажеростроение — 2023
|
||||
</EventTitle>
|
||||
@@ -65,7 +65,7 @@ function LinkButton({ href }: { href: string }) {
|
||||
const hovered = useHover(ref);
|
||||
|
||||
return (
|
||||
<div className="w-fit self-start">
|
||||
<div className="self-start w-fit">
|
||||
<Link
|
||||
to={href}
|
||||
ref={ref}
|
||||
|
||||
@@ -5,7 +5,7 @@ export function ModalContainer() {
|
||||
|
||||
return (
|
||||
modal && (
|
||||
<div className="fixed top-0 left-0 z-50 w-full h-full flex justify-center items-center bg-black bg-opacity-40 transition-opacity">
|
||||
<div className="fixed top-0 left-0 z-50 flex items-center justify-center w-full h-full transition-opacity bg-black bg-opacity-40">
|
||||
<div onClick={e => e.stopPropagation()} className="cursor-default">
|
||||
{modal}
|
||||
</div>
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import { FormEvent, useEffect, useRef, useState } from 'react';
|
||||
import { FormEvent, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import ReactInputMask from 'react-input-mask';
|
||||
import { ArrowRightIcon } from '../icons/ArrowRightIcon';
|
||||
import { CloseIcon } from '../icons/CloseIcon';
|
||||
import { LoaderIcon } from '../icons/LoaderIcon';
|
||||
import { MailIcon } from '../icons/MailIcon';
|
||||
import { phoneCodes } from '../../consts/phoneCodes';
|
||||
import { api } from '../../api/contactsFormInstance';
|
||||
import { Button } from '../../ui/Button';
|
||||
import { PhoneCode } from '../../types/PhoneCode';
|
||||
import { ChevronUpIcon } from '../icons/ChevronUpIcon';
|
||||
import { ChevronDownIcon } from '../icons/ChevronDownIcon';
|
||||
import { useModalStore } from '../../store/modalStore';
|
||||
import { SelectPhoneCode } from './SelectPhoneCode';
|
||||
import { Country } from 'react-phone-number-input';
|
||||
import { getExampleNumber } from 'libphonenumber-js';
|
||||
import examples from 'libphonenumber-js/mobile/examples';
|
||||
|
||||
export function ModalWithForm() {
|
||||
const { setModal } = useModalStore();
|
||||
const [name, setName] = useState('');
|
||||
const [phoneCode, setPhoneCode] = useState<PhoneCode>('+7');
|
||||
const [[phoneCode, country], setPhoneCodeAndCountry] = useState<
|
||||
[string, Country]
|
||||
>(['+7', 'RU']);
|
||||
const [phone, setPhone] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSend, setIsSend] = useState(false);
|
||||
|
||||
const placeholder = useMemo(
|
||||
() =>
|
||||
getExampleNumber(country, examples)
|
||||
?.formatInternational()
|
||||
.split(' ')
|
||||
.slice(1)
|
||||
.join(' '),
|
||||
[country],
|
||||
);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -79,11 +89,11 @@ export function ModalWithForm() {
|
||||
<div className="fixed flex flex-col gap-4 top-0 right-0 h-full sm:w-[408px] w-full bg-[#14161F] overflow-y-auto sm:p-8 p-6">
|
||||
{!isSend ? (
|
||||
<div className="space-y-8">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-medium accent">Оставьте заявку</p>
|
||||
<button
|
||||
onClick={() => setModal(null)}
|
||||
className="p-2 lg:hover:bg-white lg:hover:bg-opacity-10 transition-colors rounded-full"
|
||||
className="p-2 transition-colors rounded-full lg:hover:bg-white lg:hover:bg-opacity-10"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
@@ -119,20 +129,20 @@ export function ModalWithForm() {
|
||||
</label>
|
||||
<div className="flex gap-x-3 py-2 border-[#3D425C] relative">
|
||||
<SelectPhoneCode
|
||||
currentPhoneCode={phoneCode}
|
||||
onClick={setPhoneCode}
|
||||
currentPhoneCodeAndCountry={[phoneCode, country]}
|
||||
onClick={setPhoneCodeAndCountry}
|
||||
/>
|
||||
<div className="border-l border-[#3D425C]" />
|
||||
<ReactInputMask
|
||||
required
|
||||
type="tel"
|
||||
id="tel"
|
||||
mask={'(999) 99 999 99'}
|
||||
id={'tel'}
|
||||
mask={placeholder?.replace(/\d/g, '9') ?? ''}
|
||||
maskChar={null}
|
||||
value={phone}
|
||||
placeholder="(900) 000 00 00"
|
||||
onChange={e => setPhone(e.target.value)}
|
||||
className="bg-transparent rounded-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none peer"
|
||||
placeholder={placeholder}
|
||||
onChange={e => setPhone(e.target.value.replace(/ /g, ''))}
|
||||
className="w-full transition-all bg-transparent rounded-none outline-none h4 placeholder:h4 placeholder:font-medium placeholder:select-none peer"
|
||||
/>
|
||||
<div className="bottom-0 absolute w-full border-b border-[#3D425C] peer-focus:border-white -mb-2" />
|
||||
</div>
|
||||
@@ -201,7 +211,7 @@ export function ModalWithForm() {
|
||||
) : (
|
||||
<div className="">
|
||||
<div className="space-y-8">
|
||||
<h2 className="h2 font-medium">Спасибо за отправку заявки!</h2>
|
||||
<h2 className="font-medium h2">Спасибо за отправку заявки!</h2>
|
||||
<p className="m-text">
|
||||
Мы ценим ваш интерес к нашей компании и в ближайшее время свяжемся
|
||||
с вами для уточнения деталей проекта.
|
||||
@@ -221,45 +231,257 @@ export function ModalWithForm() {
|
||||
);
|
||||
}
|
||||
|
||||
function SelectPhoneCode({
|
||||
currentPhoneCode,
|
||||
onClick,
|
||||
}: {
|
||||
currentPhoneCode: PhoneCode;
|
||||
onClick: (phoneCode: PhoneCode) => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
// function SelectPhoneCode({
|
||||
// currentPhoneCode,
|
||||
// onClick,
|
||||
// }: {
|
||||
// currentPhoneCode: PhoneCode;
|
||||
// onClick: (phoneCode: PhoneCode) => void;
|
||||
// }) {
|
||||
// const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col">
|
||||
<button
|
||||
className="flex gap-x-4 items-center relative"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
setOpen(prev => !prev);
|
||||
}}
|
||||
>
|
||||
<p className="h4">{currentPhoneCode}</p>
|
||||
{open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute z-10 bg-[#14161F] top-[100%] w-[calc(100%+4px)] -left-1 border border-t-0 p-1 rounded-b-lg border-[#3D425C]">
|
||||
{phoneCodes
|
||||
.filter(phonecode => phonecode !== currentPhoneCode)
|
||||
.map(phoneCode => (
|
||||
<p
|
||||
key={phoneCode}
|
||||
className="h4 cursor-pointer hover:bg-[#3D425C] py-1"
|
||||
onClick={() => {
|
||||
onClick(phoneCode);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{phoneCode}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div className="relative flex flex-col">
|
||||
// <button
|
||||
// className="relative flex items-center gap-x-4"
|
||||
// onClick={e => {
|
||||
// e.preventDefault();
|
||||
// setOpen(prev => !prev);
|
||||
// }}
|
||||
// >
|
||||
// <p className="h4">{currentPhoneCode}</p>
|
||||
// {open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
// </button>
|
||||
// {open && (
|
||||
// <div className="absolute z-10 bg-[#14161F] top-[100%] w-[calc(100%+4px)] -left-1 border border-t-0 p-1 rounded-b-lg border-[#3D425C]">
|
||||
// {phoneCodes
|
||||
// .filter(phonecode => phonecode !== currentPhoneCode)
|
||||
// .map(phoneCode => (
|
||||
// <p
|
||||
// key={phoneCode}
|
||||
// className="h4 cursor-pointer hover:bg-[#3D425C] py-1"
|
||||
// onClick={() => {
|
||||
// onClick(phoneCode);
|
||||
// setOpen(false);
|
||||
// }}
|
||||
// >
|
||||
// {phoneCode}
|
||||
// </p>
|
||||
// ))}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// import ReactInputMask from 'react-input-mask';
|
||||
// import { SelectPhoneCode } from './SelectPhoneCode';
|
||||
// import { ClassNameWrapper } from '../../hocs/ClassNameWrapper';
|
||||
// import { LoaderIcon } from '../icons/LoaderIcon';
|
||||
// import { Button } from '../../ui/Button';
|
||||
// import { ArrowRightIcon } from '../icons/ArrowRightIcon';
|
||||
// import { FormEvent, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// import { Country } from 'react-phone-number-input';
|
||||
// import { api } from '../../api/contactsFormInstance';
|
||||
// import { getExampleNumber } from 'libphonenumber-js';
|
||||
// import examples from 'libphonenumber-js/mobile/examples';
|
||||
|
||||
// export function ModalWithForm({
|
||||
// inModal = true,
|
||||
// send = () => {},
|
||||
// }: {
|
||||
// inModal?: boolean;
|
||||
// send?: () => void;
|
||||
// }) {
|
||||
// const [name, setName] = useState('');
|
||||
// const [[phoneCode, country], setPhoneCodeAndCountry] = useState<
|
||||
// [string, Country]
|
||||
// >(['+7', 'RU']);
|
||||
// const [phone, setPhone] = useState('');
|
||||
// const [email, setEmail] = useState('');
|
||||
// const [description, setDescription] = useState('');
|
||||
// const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (textAreaRef.current) {
|
||||
// textAreaRef.current.style.height = 'auto';
|
||||
// textAreaRef.current.style.height =
|
||||
// textAreaRef.current.scrollHeight + 'px';
|
||||
// }
|
||||
// }, [textAreaRef, description]);
|
||||
|
||||
// function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
||||
// e.preventDefault();
|
||||
// sendMail();
|
||||
// }
|
||||
|
||||
// async function sendMail() {
|
||||
// setIsLoading(true);
|
||||
|
||||
// try {
|
||||
// await api
|
||||
// .post('mail', {
|
||||
// json: {
|
||||
// fullname: name,
|
||||
// phone: phoneCode + phone,
|
||||
// email,
|
||||
// request: description,
|
||||
// },
|
||||
// })
|
||||
// .json();
|
||||
|
||||
// setIsLoading(false);
|
||||
// send?.();
|
||||
// } catch (error) {
|
||||
// setIsLoading(false);
|
||||
// if (error instanceof Error) {
|
||||
// alert(error.message);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// const placeholder = useMemo(
|
||||
// () =>
|
||||
// getExampleNumber(country, examples)
|
||||
// ?.formatInternational()
|
||||
// .split(' ')
|
||||
// .slice(1)
|
||||
// .join(' '),
|
||||
// [country],
|
||||
// );
|
||||
|
||||
// return (
|
||||
// <form
|
||||
// onSubmit={handleSubmit}
|
||||
// className={
|
||||
// inModal
|
||||
// ? 'space-y-6'
|
||||
// : 'lg:space-y-12 sm:space-y-36 space-y-8 lg:max-w-[66vw] sm:max-w-[calc(369/720*100%)]'
|
||||
// }
|
||||
// >
|
||||
// <div className="space-y-6">
|
||||
// <div
|
||||
// className={
|
||||
// 'grid gap-x-4 items-start ' +
|
||||
// (inModal ? 'gap-y-6' : 'lg:grid-cols-3 max-lg:gap-y-4')
|
||||
// }
|
||||
// >
|
||||
// <div className="w-full">
|
||||
// <label
|
||||
// className="m-text text-[#9299BD] select-none"
|
||||
// htmlFor={'name' + +inModal}
|
||||
// >
|
||||
// Имя
|
||||
// </label>
|
||||
// <input
|
||||
// required
|
||||
// id={'name' + +inModal}
|
||||
// type="text"
|
||||
// value={name}
|
||||
// onChange={e => setName(e.target.value)}
|
||||
// placeholder="Ваше имя"
|
||||
// className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// <div className="w-full">
|
||||
// <label
|
||||
// className="m-text text-[#9299BD] select-none"
|
||||
// htmlFor={'email' + +inModal}
|
||||
// >
|
||||
// Email*
|
||||
// </label>
|
||||
// <input
|
||||
// required
|
||||
// id={'email' + +inModal}
|
||||
// type="text"
|
||||
// value={email}
|
||||
// onChange={e => setEmail(e.target.value)}
|
||||
// placeholder="Ваш email"
|
||||
// className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// <div className="w-full">
|
||||
// <label
|
||||
// className="m-text text-[#9299BD] select-none"
|
||||
// htmlFor={'tel' + +inModal}
|
||||
// >
|
||||
// Телефон
|
||||
// </label>
|
||||
// <div className="flex gap-x-3 py-4 border-[#3D425C] relative">
|
||||
// <SelectPhoneCode
|
||||
// currentPhoneCodeAndCountry={[phoneCode, country]}
|
||||
// onClick={setPhoneCodeAndCountry}
|
||||
// />
|
||||
// <div className="border-l border-[#3D425C]" />
|
||||
// <ReactInputMask
|
||||
// required
|
||||
// type="tel"
|
||||
// id={'tel' + +inModal}
|
||||
// mask={placeholder?.replace(/\d/g, '9') ?? ''}
|
||||
// maskChar={null}
|
||||
// value={phone}
|
||||
// placeholder={placeholder}
|
||||
// onChange={e => setPhone(e.target.value.replace(/ /g, ''))}
|
||||
// className="w-full transition-all bg-transparent rounded-none outline-none h4 placeholder:h4 placeholder:font-medium placeholder:select-none peer"
|
||||
// />
|
||||
// <div className="bottom-0 absolute w-full border-b border-[#3D425C] peer-focus:border-white -mb-px" />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div>
|
||||
// <label
|
||||
// className="m-text text-[#9299BD] select-none"
|
||||
// htmlFor={'description' + +inModal}
|
||||
// >
|
||||
// Задача
|
||||
// </label>
|
||||
// <textarea
|
||||
// ref={textAreaRef}
|
||||
// id={'description' + +inModal}
|
||||
// placeholder="Опишите вашу задачу"
|
||||
// value={description}
|
||||
// rows={1}
|
||||
// onChange={e => setDescription(e.target.value)}
|
||||
// className="bg-transparent border-b py-4 focus:border-white max-h-[300px] h-auto rounded-none border-[#3D425C] resize-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none focus:overflow-y-scroll overflow-hidden"
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="space-y-4 lg:max-w-[25vw] sm:max-lg:mt-36">
|
||||
// <Button
|
||||
// width="full"
|
||||
// disabled={isLoading}
|
||||
// className="p-2 pl-8"
|
||||
// icon={
|
||||
// isLoading ? (
|
||||
// <ClassNameWrapper
|
||||
// element={<LoaderIcon />}
|
||||
// className="relative w-5 h-5 animate-spin"
|
||||
// />
|
||||
// ) : (
|
||||
// <div className="p-2 bg-white rounded-full">
|
||||
// <ClassNameWrapper
|
||||
// element={<ArrowRightIcon />}
|
||||
// className="w-5 h-5 text-black"
|
||||
// />
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
// >
|
||||
// Отправить
|
||||
// </Button>
|
||||
// <p className="m-text text-[#52587A]">
|
||||
// *нажимая кнопку отправить, вы принимаете
|
||||
// <span className="text-gradient">
|
||||
// {' '}
|
||||
// условия использования и политику конфиденциальности
|
||||
// </span>
|
||||
// </p>
|
||||
// </div>
|
||||
// </form>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// import { Marquee } from '../Main/Marquee';
|
||||
|
||||
export function Motivation() {
|
||||
return (
|
||||
<div>
|
||||
<div className="lg:py-28 sm:py-12 py-14 grid grid-cols-12">
|
||||
<div className="grid grid-cols-12 lg:py-28 sm:py-12 py-14">
|
||||
<h1 className="2xl:mb-[38px] pb-8 font-medium lg:block max-lg:hidden h1 col-span-full">
|
||||
Создаем
|
||||
<span className="text-gradient"> интерактивные тренажеры </span>
|
||||
@@ -18,7 +16,6 @@ export function Motivation() {
|
||||
производительность
|
||||
</h3>
|
||||
</div>
|
||||
{/* <Marquee /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export function Products() {
|
||||
return (
|
||||
<div
|
||||
id="products"
|
||||
className="lg:pt-[100px] sm:max-lg:pt-[70px] max-sm:py-14 lg:-mx-10 sm:-mx-6 -mx-4 sm:space-y-[500px]"
|
||||
className="lg:pt-[100px] sm:max-lg:pt-[70px] max-sm:py-14 lg:-mx-10 sm:-mx-6 -mx-4 sm:space-y-[500px] space-y-10"
|
||||
>
|
||||
<IndustrialTrainings ref={ref1} sticked={stiked1} />
|
||||
<Simulators ref={ref2} sticked={stiked2} />
|
||||
|
||||
@@ -3,45 +3,13 @@ import { useHover } from 'usehooks-ts';
|
||||
import { getIcon } from '../../../../utils/getIcon';
|
||||
import { useInView } from 'framer-motion';
|
||||
|
||||
function ForTeachingOption({
|
||||
title,
|
||||
description,
|
||||
type,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'labs' | 'teaching';
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const hovered = useHover(ref);
|
||||
const isInView = useInView(ref, {
|
||||
margin: `0px 0px -${window.innerHeight - (ref.current?.clientHeight ?? 0)}px`,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex gap-x-7 items-start sm:max-lg:pr-3 max-sm:pb-4 max-sm:border-b border-[#3D425C]"
|
||||
>
|
||||
{getIcon(type, hovered, 'max-sm:hidden min-w-11')}
|
||||
<div className="lg:pl-4 sm:pl-[13px] sm:border-l border-[#3D425C]">
|
||||
<div className="flex items-center sm:items-start sm:max-lg:flex-col gap-x-2 mb-1">
|
||||
{getIcon(type, hovered || isInView, 'sm:hidden min-w-11')}
|
||||
<h4 className="font-medium lg:text-2xl sm:text-xl">{title}</h4>
|
||||
</div>
|
||||
<p className="opacity-60 l-text">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const ForTeaching = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="lg:ml-[129px] lg:min-h-[calc(100vh-276px)] lg:min-w-[calc(100vw-129px)] sm:min-h-[calc(100vh-176px)] min-h-[calc(100vh)] min-w-[100vw] sm:sticky z-50 lg:top-[276px] sm:top-[176px] top-0 lg:px-10 lg:pt-10 sm:px-6 sm:pt-6 px-4 pt-4 lg:border-l bg-[#14161F] border-t border-[#3D425C]"
|
||||
>
|
||||
<h2 className={'h2 font-medium flex justify-between'}>
|
||||
<h2 className="flex justify-between font-medium h2">
|
||||
Интерактивные тренажеры для учебных заведений
|
||||
<p className="h3 font-medium text-[#3D425C]">03</p>
|
||||
</h2>
|
||||
@@ -59,7 +27,7 @@ export const ForTeaching = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
поломки оборудования, а также экономить на расходных средствах"
|
||||
type="labs"
|
||||
/>
|
||||
<p className="lg:text-2xl sm:text-xl font-medium">
|
||||
<p className="font-medium lg:text-2xl sm:text-xl">
|
||||
Оснащение учебных классов и центров всем необходимым для
|
||||
современного обучения под «ключ»
|
||||
</p>
|
||||
@@ -72,10 +40,42 @@ export const ForTeaching = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
/>
|
||||
<img
|
||||
src="src/assets/products/teaching/teaching_mobile.png"
|
||||
className="sm:hidden"
|
||||
className="mt-5 -mx-6 sm:hidden"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
function ForTeachingOption({
|
||||
title,
|
||||
description,
|
||||
type,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'labs' | 'teaching';
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const hovered = useHover(ref);
|
||||
const isInView = useInView(ref, {
|
||||
margin: `0px 0px ${(ref.current?.clientHeight ?? 0) - window.innerHeight}px`,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex sm:gap-x-7 items-start sm:max-lg:pr-3 max-sm:pb-4 max-sm:border-b border-[#3D425C]"
|
||||
>
|
||||
{getIcon(type, hovered, 'max-sm:hidden min-w-11')}
|
||||
<div className="lg:pl-4 sm:pl-[13px] sm:border-l border-[#3D425C]">
|
||||
<div className="flex items-center mb-1 sm:items-start sm:max-lg:flex-col gap-x-2">
|
||||
{getIcon(type, hovered || isInView, 'sm:hidden min-w-11')}
|
||||
<h4 className="font-medium lg:text-2xl sm:text-xl">{title}</h4>
|
||||
</div>
|
||||
<p className="opacity-60 l-text">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ function TeachingItem({
|
||||
>
|
||||
{getIcon(iconType, hovered, 'max-sm:hidden sm:max-lg:mb-[14px] min-w-11')}
|
||||
<div className="sm:border-l border-[#3D425C] sm:pl-4">
|
||||
<h4 className="flex items-center gap-x-2 font-medium lg:text-2xl sm:text-xl mb-2">
|
||||
<h4 className="flex items-center mb-2 font-medium gap-x-2 lg:text-2xl sm:text-xl">
|
||||
{getIcon(iconType, hovered || isInView, 'sm:hidden min-w-11')}
|
||||
{title}
|
||||
</h4>
|
||||
@@ -42,9 +42,9 @@ export const IndustrialTrainings = forwardRef<
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="sm:sticky top-0 min-h-[100svh] min-w-[100vw] overflow-hidden lg:px-10 lg:pt-10 sm:px-6 sm:pt-6 px-4 pt-4 border-t border-[#3D425C] bg-[#14161F] max-sm:flex flex-col items-center gap-y-4"
|
||||
className="sm:sticky top-0 min-h-[100svh] min-w-[100vw] overflow-hiddens lg:px-10 lg:pt-10 sm:px-6 sm:pt-6 px-4 pt-4 border-t border-[#3D425C] bg-[#14161F] max-sm:flex flex-col items-center gap-y-4"
|
||||
>
|
||||
<div className="lg:space-y-14 sm:space-y-10 space-y-6">
|
||||
<div className="space-y-6 lg:space-y-14 sm:space-y-10">
|
||||
<h2
|
||||
className={
|
||||
'h2 font-medium w-full flex justify-between items-center' +
|
||||
@@ -80,22 +80,22 @@ export const IndustrialTrainings = forwardRef<
|
||||
<img
|
||||
src="src/assets/products/trainings/trainings_desktop.png"
|
||||
className={
|
||||
'absolute right-0 top-[calc(121px)] object-cover lg:w-[calc(1000/1600*100vw)] xl:w-[calc(1152/1600*100vw)] max-lg:hidden' +
|
||||
(sticked ? ' transition-opacity opacity-0' : ' opacity-100')
|
||||
'absolute right-0 top-[121px] object-cover lg:w-[calc(1000/1600*100vw)] xl:w-[calc(1152/1600*100vw)] max-lg:hidden ' +
|
||||
(sticked ? 'transition-opacity opacity-0' : 'opacity-100')
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="src/assets/products/trainings/trainings_tablet.png"
|
||||
className={
|
||||
'absolute right-0 top-[120px] object-cover w-[calc(438/768*100vw)] hidden sm:max-lg:block' +
|
||||
(sticked ? ' transition-opacity opacity-0' : ' opacity-100')
|
||||
'absolute right-0 top-[120px] object-cover w-[calc(438/768*100vw)] hidden sm:max-lg:block ' +
|
||||
(sticked ? 'transition-opacity opacity-0' : 'opacity-100')
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="src/assets/products/trainings/trainings_mobile.png"
|
||||
className="sm:hidden object-cover object-center"
|
||||
className="object-cover object-center sm:hidden"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ import { IProject, Media } from '../../types/Project';
|
||||
// </Title>
|
||||
// <MiniTitle
|
||||
// text="реализованные проекты"
|
||||
// className="lg:ml-10 sm:ml-6 ml-4"
|
||||
// className="ml-4 lg:ml-10 sm:ml-6"
|
||||
// />
|
||||
// <Slider projects={projects} />
|
||||
// </div>
|
||||
@@ -48,15 +48,15 @@ export const Project = forwardRef<HTMLDivElement, IProject<Media>>(
|
||||
const [buffering, setBuffering] = useState(true);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="aspect-square flex flex-col relative">
|
||||
<div ref={ref} className="relative flex flex-col aspect-square">
|
||||
{media === Media.img ? (
|
||||
<div
|
||||
className="bg-cover bg-center bg-no-repeat flex-1"
|
||||
className="flex-1 bg-center bg-no-repeat bg-cover"
|
||||
style={{ backgroundImage: `url(${src})` }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="flex-1 overflow-hidden relative flex justify-center items-center bg-cover bg-center bg-no-repeat"
|
||||
className="relative flex items-center justify-center flex-1 overflow-hidden bg-center bg-no-repeat bg-cover"
|
||||
style={{ backgroundImage: `url(${src[1]})` }}
|
||||
>
|
||||
<video
|
||||
@@ -74,7 +74,7 @@ export const Project = forwardRef<HTMLDivElement, IProject<Media>>(
|
||||
<div className="flex flex-col justify-between gap-2 my-4">
|
||||
<div className="flex justify-between">
|
||||
<h4 className="font-medium h4">{title}</h4>
|
||||
<p className="h4 font-medium">{year}</p>
|
||||
<p className="font-medium h4">{year}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{tags.map(tag => (
|
||||
@@ -156,7 +156,7 @@ Project.displayName = 'Project';
|
||||
// }, [sliderOffset, order, slide]);
|
||||
|
||||
// return (
|
||||
// <div className="flex flex-col lg:mt-4 sm:mt-3 mt-2 lg:-mx-10 sm:-mx-6 -mx-4 relative">
|
||||
// <div className="relative flex flex-col mt-2 -mx-4 lg:mt-4 sm:mt-3 lg:-mx-10 sm:-mx-6">
|
||||
// <div {...handlers}>
|
||||
// <div
|
||||
// ref={ref}
|
||||
@@ -180,7 +180,7 @@ Project.displayName = 'Project';
|
||||
// dispatch(-1);
|
||||
// }
|
||||
// }}
|
||||
// className="max-sm:hidden outline-none"
|
||||
// className="outline-none max-sm:hidden"
|
||||
// >
|
||||
// <ArrowLeftIcon />
|
||||
// </button>
|
||||
@@ -204,7 +204,7 @@ Project.displayName = 'Project';
|
||||
// dispatch(1);
|
||||
// }
|
||||
// }}
|
||||
// className="max-sm:hidden outline-none"
|
||||
// className="outline-none max-sm:hidden"
|
||||
// >
|
||||
// <ArrowRightIcon />
|
||||
// </button>
|
||||
|
||||
@@ -8,7 +8,10 @@ export function ProjectsSlider() {
|
||||
const width = useWindowWidth();
|
||||
|
||||
return (
|
||||
<div className="lg:space-y-14 space-y-6 lg:pt-[100px] sm:py-[70px] py-14">
|
||||
<div
|
||||
id="projects"
|
||||
className="lg:space-y-14 space-y-6 lg:pt-[100px] sm:py-[70px] py-14"
|
||||
>
|
||||
<Title>
|
||||
<span className="text-gradient">Большой опыт в работе</span> с
|
||||
промышленными предприятиями и учебными заведениями
|
||||
|
||||
@@ -5,43 +5,43 @@ import { ArrowDownIcon } from '../icons/ArrowDownIcon';
|
||||
export function RecentProjects() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<MiniTitle text="последние проекты" />
|
||||
<HashLink
|
||||
className="flex items-center gap-x-2 p-2 bg-[#3D425C] rounded-full"
|
||||
to={'#projects'}
|
||||
>
|
||||
<span className="btn-text font-medium ml-3">Смотреть все</span>
|
||||
<div className="bg-white rounded-full p-1">
|
||||
<span className="ml-3 font-medium btn-text">Смотреть все</span>
|
||||
<div className="p-1 bg-white rounded-full">
|
||||
<ArrowDownIcon className="w-5 h-5 text-black" />
|
||||
</div>
|
||||
</HashLink>
|
||||
</div>
|
||||
<div className="flex justify-stretch gap-x-4 min-w-[calc(752/1600*100%)]">
|
||||
<div className="flex-1 flex flex-col gap-y-6">
|
||||
<div className="flex flex-col flex-1 gap-y-6">
|
||||
<img src="src/assets/recent_projects/plane.png" className="" alt="" />
|
||||
<div className="space-y-2">
|
||||
<p className="h3 font-medium">L 410 NG Aircraft</p>
|
||||
<p className="font-medium h3">L 410 NG Aircraft</p>
|
||||
<div className="flex items-center gap-x-6">
|
||||
<p className="flex items-center gap-x-2 h4 font-medium py-2">
|
||||
<p className="flex items-center py-2 font-medium gap-x-2 h4">
|
||||
<div className="w-3 h-3 bg-white" />
|
||||
Презентация
|
||||
</p>
|
||||
<p className="flex items-center gap-x-2 h4 font-medium py-2">
|
||||
<p className="flex items-center py-2 font-medium gap-x-2 h4">
|
||||
<div className="w-3 h-3 bg-white" />
|
||||
3D-макет
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col gap-y-6">
|
||||
<div className="flex flex-col flex-1 gap-y-6">
|
||||
<img src="src/assets/recent_projects/laba.png" className="" alt="" />
|
||||
<div className="space-y-2">
|
||||
<p className="h3 font-medium">
|
||||
<p className="font-medium h3">
|
||||
Учебная лаборатория определения жирности молока
|
||||
</p>
|
||||
<div className="flex items-center gap-x-6">
|
||||
<p className="flex items-center gap-x-2 h4 font-medium py-2">
|
||||
<p className="flex items-center py-2 font-medium gap-x-2 h4">
|
||||
<div className="w-3 h-3 bg-white" />
|
||||
VR-приложение
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import countries from 'countries-phone-masks';
|
||||
import {
|
||||
CountryCode,
|
||||
getCountries,
|
||||
getCountryCallingCode,
|
||||
} from 'libphonenumber-js';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { ChevronUpIcon } from '../icons/ChevronUpIcon';
|
||||
import { ChevronDownIcon } from '../icons/ChevronDownIcon';
|
||||
import { ClassNameWrapper } from '../../hocs/ClassNameWrapper';
|
||||
|
||||
export function SelectPhoneCode({
|
||||
currentPhoneCodeAndCountry: [currentPhoneCode, currentCountry],
|
||||
onClick,
|
||||
}: {
|
||||
currentPhoneCodeAndCountry: [string, CountryCode];
|
||||
onClick: ([phoneCode, country]: [string, CountryCode]) => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useOnClickOutside(ref, () => setOpen(false));
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative flex flex-col sm:w-1/3 max-w-[350px]">
|
||||
<button
|
||||
className="relative flex items-center justify-between gap-x-1"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
setOpen(prev => !prev);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={countries.find(c => c.iso === currentCountry)?.flag}
|
||||
className="w-4 sm:w-6"
|
||||
alt=""
|
||||
/>
|
||||
<p className="h4">{currentPhoneCode}</p>
|
||||
<ClassNameWrapper
|
||||
className="flex-1 max-sm:w-4 sm:max-lg:w-5"
|
||||
element={open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
/>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="space-y-1 absolute z-10 bg-[#14161F] top-[100%] -left-1 border border-t-0 rounded-b-lg border-[#3D425C] max-h-[300px] overflow-y-auto overflow-x-hidden">
|
||||
{getCountries()
|
||||
.map(country => [`+${getCountryCallingCode(country)}`, country])
|
||||
.filter(
|
||||
([phonecode, country]) =>
|
||||
phonecode !== currentPhoneCode || country !== currentCountry,
|
||||
)
|
||||
.map(([phoneCode, country]) => (
|
||||
<div
|
||||
key={country}
|
||||
className="flex items-center gap-x-1 hover:bg-[#3D425C] px-1"
|
||||
onClick={() => {
|
||||
onClick([phoneCode, country as CountryCode]);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={countries.find(c => c.iso === country)?.flag}
|
||||
alt=""
|
||||
className="w-4 sm:w-6"
|
||||
/>
|
||||
<p className="flex-1 py-1 cursor-pointer h4">{phoneCode}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export function SliderControls({
|
||||
strokeDashoffset={`-${height / 2}`}
|
||||
/>
|
||||
</svg>
|
||||
<p className="h4 font-medium absolute self-center">
|
||||
<p className="absolute self-center font-medium h4">
|
||||
{Math.round(slide) + 1} из {slidesCount}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Teaching() {
|
||||
|
||||
function TeachingFeaturesForDesktop() {
|
||||
return (
|
||||
<div className="max-lg:hidden space-y-4 col-span-7">
|
||||
<div className="col-span-7 space-y-4 max-lg:hidden">
|
||||
<div className="p-10 relative aspect-[752/400] border border-[#3D425C] bg-[url(src/assets/teaching/highlight_desktop.png)] bg-[length:0%] hover:bg-[length:100%] bg-no-repeat bg-right-top transition-all overflow-hidden flex justify-between">
|
||||
<div className="space-y-2 max-w-[calc(380/1600*100vw)]">
|
||||
<TeachingFeatureTitle>Управление процессом</TeachingFeatureTitle>
|
||||
@@ -54,7 +54,7 @@ function TeachingFeaturesForDesktop() {
|
||||
</div>
|
||||
<img
|
||||
src="src/assets/teaching/modal.png"
|
||||
className="rounded-lg -mr-10"
|
||||
className="-mr-10 rounded-lg"
|
||||
alt="Управление пользователями"
|
||||
/>
|
||||
</div>
|
||||
@@ -105,7 +105,7 @@ function TeachingFeaturesForDesktop() {
|
||||
|
||||
function TeachingFeaturesForOtherScreens() {
|
||||
return (
|
||||
<div className="lg:hidden sm:-mx-6 -mx-4 flex flex-col">
|
||||
<div className="flex flex-col -mx-4 lg:hidden sm:-mx-6">
|
||||
<div className="sm:flex justify-between max-sm:relative sm:aspect-[768/240] aspect-[6/5] order-1 border-t border-[#3D425C] sm:bg-[url(src/assets/teaching/highlight_tablet.png)] bg-[url(src/assets/teaching/highlight_mobile.png)] bg-[length:0%] hover:bg-[length:100%] bg-no-repeat bg-bottom transition-all sm:pt-6 sm:px-6 pt-5 px-5 overflow-hidden">
|
||||
<div className="space-y-1">
|
||||
<TeachingFeatureTitle>
|
||||
|
||||
@@ -13,7 +13,7 @@ export function Trainings() {
|
||||
<span className="text-gradient">варианты комплектации тренажеров</span>,
|
||||
основываясь на специфике вашего тренировочного процесса
|
||||
</Title>
|
||||
<div className="sm:grid lg:grid-cols-12 grid-cols-8 gap-x-4">
|
||||
<div className="grid-cols-8 sm:grid lg:grid-cols-12 gap-x-4">
|
||||
<TrainingsFeature
|
||||
order={1}
|
||||
src="src/assets/trainings/vr.png"
|
||||
@@ -58,25 +58,25 @@ function TrainingsFeature({
|
||||
ref={ref}
|
||||
className="lg:first:h-[200px] lg:last:h-[200px] lg:h-[176px] sm:flex max-sm:space-y-[42px] items-stretch justify-between sm:py-10 max-sm:pt-5"
|
||||
>
|
||||
<div className="sm:space-y-4 lg:w-1/3 sm:w-1/2 col-span-1">
|
||||
<div className="col-span-1 sm:space-y-4 lg:w-1/3 sm:w-1/2">
|
||||
<h3 className="font-medium max-sm:mb-2 h3">{title}</h3>
|
||||
<p className="opacity-60 l-text">{text}</p>
|
||||
</div>
|
||||
<div className="flex sm:hidden justify-between items-end">
|
||||
<div className="flex items-end justify-between sm:hidden">
|
||||
<p className="text-[#52587A] m-text mb-5">[0{order}]</p>
|
||||
<div className="flex flex-col items-center">
|
||||
<img src={src} alt={title} className="relative z-30 w-[50vw]" />
|
||||
<VrBacklightIcon className="absolute w-[36vw] h-fit" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:flex hidden">
|
||||
<div className="hidden md:flex">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: +hovered, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.4,
|
||||
}}
|
||||
className="-my-10 hidden lg:flex items-center justify-center"
|
||||
className="items-center justify-center hidden -my-10 lg:flex"
|
||||
>
|
||||
<img
|
||||
src={src}
|
||||
@@ -85,7 +85,7 @@ function TrainingsFeature({
|
||||
/>
|
||||
<VrBacklightIcon className="absolute w-[24vw]" />
|
||||
</motion.div>
|
||||
<div className="lg:hidden flex items-center justify-center">
|
||||
<div className="flex items-center justify-center lg:hidden">
|
||||
<img
|
||||
src={src}
|
||||
className="w-[27vw] relative z-20 h-[calc(27vw*0.6)]"
|
||||
|
||||
@@ -24,18 +24,18 @@ export function Video() {
|
||||
</button>
|
||||
</div>
|
||||
{open && (
|
||||
<div className="fixed top-0 left-0 z-50 w-full h-full flex justify-center items-center overflow-hidden">
|
||||
<div className="fixed top-0 left-0 z-50 flex items-center justify-center w-full h-full overflow-hidden">
|
||||
<div className="cursor-default" onClick={e => e.stopPropagation()}>
|
||||
<div className="absolute top-0 left-0 w-screen h-screen overflow-hidden flex justify-center items-start">
|
||||
<div className="aspect-video w-full">
|
||||
<div className="absolute top-0 left-0 flex items-start justify-center w-screen h-screen overflow-hidden">
|
||||
<div className="w-full aspect-video">
|
||||
<button
|
||||
className="absolute top-6 right-6 p-6 z-60 rounded-full border border-white outline-none"
|
||||
className="absolute p-6 border border-white rounded-full outline-none top-6 right-6 z-60"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
<iframe
|
||||
className="h-full w-full"
|
||||
className="w-full h-full"
|
||||
src="https://www.youtube.com/embed/aAGcjf-B42g?si=36dEzF9t4efmUJOA"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function DangerIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function DangerIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="45"
|
||||
@@ -13,7 +7,6 @@ export function DangerIcon({
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg3710"
|
||||
className={className}
|
||||
color="currentColor"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function LaboratoriesIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function LaboratoriesIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
id="Ремонти обслуживание"
|
||||
@@ -13,7 +7,6 @@ export function LaboratoriesIcon({
|
||||
viewBox="0 0 44 44"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
color="currentColor"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function PlansIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function PlansIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="44"
|
||||
@@ -14,7 +8,6 @@ export function PlansIcon({
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg3092"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
className={className}
|
||||
color="currentColor"
|
||||
>
|
||||
<defs id="defs393"></defs>
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function ProcessesIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function ProcessesIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
id="Ремонти обслуживание"
|
||||
@@ -14,7 +8,6 @@ export function ProcessesIcon({
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M7 7L38 7L38 38"
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function SafetyIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function SafetyIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="44"
|
||||
@@ -14,7 +8,6 @@ export function SafetyIcon({
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg36"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
className={className}
|
||||
color="currentColor"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function ServiceIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function ServiceIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="44"
|
||||
@@ -13,7 +7,6 @@ export function ServiceIcon({
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg5549"
|
||||
className={className}
|
||||
color="currentColor"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
style={{ maxHeight: '100%', maxWidth: '100%' }}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function TeachSystemsIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function TeachSystemsIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
id="Ремонти обслуживание"
|
||||
@@ -14,7 +8,6 @@ export function TeachSystemsIcon({
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
color="currentColor"
|
||||
className={className}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function TeamworkIcon({
|
||||
className = '',
|
||||
animated,
|
||||
}: {
|
||||
className?: string;
|
||||
animated: boolean;
|
||||
}) {
|
||||
export function TeamworkIcon({ animated }: { animated: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
id="Ремонти обслуживание"
|
||||
@@ -13,7 +7,6 @@ export function TeamworkIcon({
|
||||
viewBox="0 0 44 44"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
|
||||
interface Props {
|
||||
element: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ClassNameWrapper({ element, className }: Props) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!className?.split(' ').length) return;
|
||||
|
||||
ref.current?.children
|
||||
.item(0)
|
||||
?.classList.add(...className.split(' ').filter(Boolean));
|
||||
}, [className, element]);
|
||||
|
||||
return <div ref={ref}>{element}</div>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { HashLink } from 'react-router-hash-link';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export function AnchorLink({
|
||||
children,
|
||||
@@ -10,16 +10,14 @@ export function AnchorLink({
|
||||
className?: string;
|
||||
}>) {
|
||||
return (
|
||||
<div>
|
||||
<HashLink
|
||||
<Link
|
||||
className={
|
||||
'btn-text font-semibold border-l border-[#3D425C] hidden py-[30px] px-10 min-[1350px]:block hover:bg-[#3D425C] active:bg-[#14161F] outline-none ' +
|
||||
'btn-text font-semibold border-l last:border-r border-[#3D425C] hidden py-[30px] px-10 xl:block hover:bg-[#3D425C] active:bg-[#14161F] outline-none ' +
|
||||
className
|
||||
}
|
||||
to={route}
|
||||
>
|
||||
{children}
|
||||
</HashLink>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export function Button({
|
||||
icon ? 'pr-4' : ''
|
||||
} flex gap-1 items-center overflow-hidden w-${width} ${className} justify-between`}
|
||||
>
|
||||
<span className="group-hover:opacity-10 opacity-0 bg-black transition-opacity absolute top-0 left-0 w-full h-full"></span>
|
||||
<span className="absolute top-0 left-0 w-full h-full transition-opacity bg-black opacity-0 group-hover:opacity-10"></span>
|
||||
<span className={'relative font-medium' + (icon ? '' : ' m-auto')}>
|
||||
{children}
|
||||
</span>
|
||||
|
||||
@@ -100,7 +100,7 @@ export function SliderWithScaling<T extends { title: string }>({
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col relative ' + className}>
|
||||
<div className="overflow-hidden lg:-mx-10 sm:-mx-6 -mx-4 h-full">
|
||||
<div className="h-full -mx-4 overflow-hidden lg:-mx-10 sm:-mx-6">
|
||||
<div {...handlers} className="h-full">
|
||||
<div
|
||||
className={`flex items-${alignItems} gap-x-4 -mr-6 select-none`}
|
||||
|
||||
+33
-8
@@ -7,6 +7,7 @@ import { SafetyIcon } from '../components/icons/SafetyIcon';
|
||||
import { TeamworkIcon } from '../components/icons/TeamworkIcon';
|
||||
import { LaboratoriesIcon } from '../components/icons/LaboratoriesIcon';
|
||||
import { TeachSystemsIcon } from '../components/icons/TeachSystemsIcon';
|
||||
import { ClassNameWrapper } from '../hocs/ClassNameWrapper';
|
||||
|
||||
export function getIcon(
|
||||
type:
|
||||
@@ -22,13 +23,37 @@ export function getIcon(
|
||||
className?: string,
|
||||
) {
|
||||
return new Map<typeof type, ReactNode>([
|
||||
['danger', DangerIcon({ className, animated })],
|
||||
['processes', ProcessesIcon({ className, animated })],
|
||||
['plans', PlansIcon({ className, animated })],
|
||||
['service', ServiceIcon({ className, animated })],
|
||||
['safety', SafetyIcon({ className, animated })],
|
||||
['teamwork', TeamworkIcon({ className, animated })],
|
||||
['labs', LaboratoriesIcon({ className, animated })],
|
||||
['teaching', TeachSystemsIcon({ className, animated })],
|
||||
[
|
||||
'danger',
|
||||
ClassNameWrapper({ element: DangerIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'processes',
|
||||
ClassNameWrapper({ element: ProcessesIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'plans',
|
||||
ClassNameWrapper({ element: PlansIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'service',
|
||||
ClassNameWrapper({ element: ServiceIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'safety',
|
||||
ClassNameWrapper({ element: SafetyIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'teamwork',
|
||||
ClassNameWrapper({ element: TeamworkIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'labs',
|
||||
ClassNameWrapper({ element: LaboratoriesIcon({ animated }), className }),
|
||||
],
|
||||
[
|
||||
'teaching',
|
||||
ClassNameWrapper({ element: TeachSystemsIcon({ animated }), className }),
|
||||
],
|
||||
]).get(type);
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export default {
|
||||
'2xl': '1600px',
|
||||
},
|
||||
animation: {
|
||||
'infinite-scroll': 'infinite-scroll 25s linear infinite',
|
||||
'infinite-scroll': 'infinite-scroll 30s linear infinite',
|
||||
},
|
||||
keyframes: {
|
||||
'infinite-scroll': {
|
||||
|
||||
@@ -923,7 +923,7 @@ chokidar@^3.5.3:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
classnames@^2.2.3:
|
||||
classnames@^2.2.3, classnames@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||
@@ -967,6 +967,16 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
countries-phone-masks@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/countries-phone-masks/-/countries-phone-masks-1.1.0.tgz#854ae21bf495a2bc3a467c47317220ea87ceac50"
|
||||
integrity sha512-ns5+L+rvkfg6qfBUawAIU9WDCpbiwcPw6n7c87B4zppaxfIBFMyD5Fte6UDAdwynDtdxa2xV5/4IL/2+Ju9nVg==
|
||||
|
||||
country-flag-icons@^1.5.11:
|
||||
version "1.5.13"
|
||||
resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.5.13.tgz#963596b7fca6602b4b389a4e7b711ef3f33cc0b1"
|
||||
integrity sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow==
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@@ -1410,6 +1420,13 @@ inherits@2:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
input-format@^0.3.10:
|
||||
version "0.3.10"
|
||||
resolved "https://registry.yarnpkg.com/input-format/-/input-format-0.3.10.tgz#e8a8855e2e89e3b1cd995333f6277c14865f0e35"
|
||||
integrity sha512-5cFv/kOZD7Ch0viprVkuYPDkAU7HBZYBx8QrIpQ6yXUWbAQ0+RQ8IIojDJOf/RO6FDJLL099HDSK2KoVZ2zevg==
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
@@ -1534,6 +1551,11 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
libphonenumber-js@^1.11.5, libphonenumber-js@^1.11.7:
|
||||
version "1.11.7"
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.7.tgz#efe4fcf816e1982925e9c800d0013b0ee99b8283"
|
||||
integrity sha512-x2xON4/Qg2bRIS11KIN9yCNYUjhtiEjNyptjX0mX+pyKHecxuJVLIpfX1lq9ZD6CrC/rB+y4GBi18c6CEcUR+A==
|
||||
|
||||
lilconfig@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||
@@ -1827,7 +1849,7 @@ prettier@^3.3.2:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a"
|
||||
integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==
|
||||
|
||||
prop-types@^15.7.2:
|
||||
prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@@ -1867,6 +1889,17 @@ react-is@^16.13.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-phone-number-input@^3.4.5:
|
||||
version "3.4.5"
|
||||
resolved "https://registry.yarnpkg.com/react-phone-number-input/-/react-phone-number-input-3.4.5.tgz#9ceeccca59283eda614f516ac6040524e88e4e01"
|
||||
integrity sha512-IlLTG0F/2P+72drqGNiYaguV3KOD4EVxQWGJ7YSofbOb6vyCWWLJqQIQsFFNpfrMrXzYtB3G+aHL9tprGfisFw==
|
||||
dependencies:
|
||||
classnames "^2.5.1"
|
||||
country-flag-icons "^1.5.11"
|
||||
input-format "^0.3.10"
|
||||
libphonenumber-js "^1.11.5"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-rangeslider@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-rangeslider/-/react-rangeslider-2.2.0.tgz#4362b01f4f5a455f0815d371d496f69ca4c6b5aa"
|
||||
@@ -1888,13 +1921,6 @@ react-router-dom@^6.23.1:
|
||||
"@remix-run/router" "1.16.1"
|
||||
react-router "6.23.1"
|
||||
|
||||
react-router-hash-link@^2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-2.4.3.tgz#570824d53d6c35ce94d73a46c8e98673a127bf08"
|
||||
integrity sha512-NU7GWc265m92xh/aYD79Vr1W+zAIXDWp3L2YZOYP4rCqPnJ6LI6vh3+rKgkidtYijozHclaEQTAHaAaMWPVI4A==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-router@6.23.1:
|
||||
version "6.23.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9"
|
||||
@@ -1907,11 +1933,6 @@ react-swipeable@^7.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.1.tgz#cd299f5986c5e4a7ee979839658c228f660e1e0c"
|
||||
integrity sha512-RKB17JdQzvECfnVj9yDZsiYn3vH0eyva/ZbrCZXZR0qp66PBRhtg4F9yJcJTWYT5Adadi+x4NoG53BxKHwIYLQ==
|
||||
|
||||
react-usestateref@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/react-usestateref/-/react-usestateref-1.0.9.tgz#d40bc54db116e786b6b2bb1cd20fe06e7f8187f3"
|
||||
integrity sha512-t8KLsI7oje0HzfzGhxFXzuwbf1z9vhBM1ptHLUIHhYqZDKFuI5tzdhEVxSNzUkYxwF8XdpOErzHlKxvP7sTERw==
|
||||
|
||||
react@^18.3.1:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
|
||||
|
||||
Reference in New Issue
Block a user