This commit is contained in:
2024-07-16 18:52:27 +05:00
parent 41e512a85f
commit e660845e4c
11 changed files with 205 additions and 208 deletions
+2 -2
View File
@@ -1,14 +1,14 @@
import { Outlet } from 'react-router-dom';
import { Motivation } from './components/Layout/Motivation';
import { Header } from './components/Layout/Header';
import { Navbar } from './components/Layout/Navbar';
import { Footer } from './components/Layout/Footer';
import ModalContainer from './components/Main/ModalContainer';
export default function Layout() {
return (
<div className="bg-[#14161F]">
<Navbar />
<Header />
<Motivation />
<main>
<Outlet />
</main>
+149 -35
View File
@@ -1,42 +1,156 @@
import { Marquee } from '../Main/Marquee';
import { motion } from 'framer-motion';
import { PropsWithChildren, useRef, useState } from 'react';
import { Anchor } from '../../ui/NavLink';
import { Link } from 'react-router-dom';
import { Lang, useLang } from '../../store/languageStore';
import { HashLink } from 'react-router-hash-link';
import { useOnClickOutside } from 'usehooks-ts';
import useModalStore from '../../store/modalStore';
import ModalWithForm from '../Main/ModalWithForm';
import Button from '../../ui/Button';
export function Header() {
const [menuOpen, setMenuOpen] = useState(false);
const { value: lang } = useLang();
const setModal = useModalStore(state => state.setModal);
const menuRef = useRef<HTMLDivElement>(null);
const menuBtnRef = useRef<HTMLButtonElement>(null);
useOnClickOutside<HTMLDivElement | HTMLButtonElement>(
[menuRef, menuBtnRef],
() => setMenuOpen(false),
);
return (
<header className="text-[#ffffff]">
<div className="lg:px-10 sm:px-6 mobile:px-4 lg:py-28 sm:py-12 mobile:py-14">
<h1 className="desktop-figma:mb-[38px] pb-8 font-medium lg:block mobile:max-lg:hidden h1">
Создаем{' '}
<span
className="bg-text-gradient bg-gradient-to-r from-[#798FFF] to-[#D375FF]"
style={{
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
<>
<nav className="flex items-stretch justify-between border-b border-[#3D425C] lg:pl-10 mobile:pl-4 lg:min-h-[72px] mobile:min-h-16">
<Link to={'/'} className="flex w-[104px] justify-between items-center">
<img src="src/assets/logo.svg" alt="" />
<img
src="src/assets/text_logo.svg"
alt=""
className="lg:block mobile:hidden"
/>
</Link>
<div className="flex">
<Anchor route="#products">Типы тренажеров</Anchor>
<Anchor route="#trainings">Варианты комплектации</Anchor>
<Anchor route="#projects">Проекты</Anchor>
<Anchor route="#events">События</Anchor>
<Button
onClick={() => setModal(<ModalWithForm />)}
className="rounded-none btn-text font-semibold sm:block mobile:hidden px-10"
>
интерактивные тренажеры
</span>{' '}
для промышленности и образования
</h1>
<h1 className="font-medium lg:hidden mobile:max-lg:block h1">
Интерактивные тренажеры{' '}
<span
className="bg-text-gradient bg-gradient-to-r from-[#798FFF] to-[#D375FF]"
style={{
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
Оставить заявку
</Button>
<LangToggler lang={lang} />
<button
ref={menuBtnRef}
onClick={() => setMenuOpen(prev => !prev)}
className="px-6 py-5 min-[1350px]:hidden mobile:block border-[#3D425C] mobile:max-sm:border-l"
>
для обучения сотрудников
</span>
</h1>
<h3 className="max-w-[768px] lg:block mobile:max-lg:hidden h3">
Помогаем сократить затраты на обучение, повысить безопасность и
производительность
</h3>
</div>
<Marquee />
</header>
<img
src={`src/assets/${menuOpen ? 'cross' : 'burger'}.svg`}
alt=""
/>
</button>
</div>
</nav>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: +menuOpen }}
transition={{ duration: 0.2 }}
ref={menuRef}
onClick={() => setMenuOpen(false)}
className={
'absolute z-50 w-full min-[1350px]:hidden sm:max-[1350px]:max-w-[340px] right-0' +
(menuOpen ? ' shadow-[0_0_0_9999px_rgba(0,0,0,.4)]' : '')
}
>
<div>
<BurgerAnchor route="#products">Типы тренажеров</BurgerAnchor>
<BurgerAnchor route="#trainings">Варианты комплектации</BurgerAnchor>
<BurgerAnchor route="#projects">Проекты</BurgerAnchor>
<BurgerAnchor route="#events">События</BurgerAnchor>
</div>
<div className="grid mobile:max-sm:grid-cols-[216px_1fr_1fr] sm:grid-cols-2 ">
<Button
onClick={() => {
setMenuOpen(false);
setModal(<ModalWithForm />);
}}
width="full"
className="sm:hidden font-semibold btn-text rounded-none"
>
Оставить заявку
</Button>
<ChooseLang lang="RU" />
<ChooseLang lang="EN" />
</div>
</motion.div>
</>
);
}
function BurgerAnchor({
children,
route,
}: PropsWithChildren<{ route: string }>) {
return (
<HashLink
to={route}
className="flex items-center px-10 py-6 gap-1 text-[#ffffff] btn-text bg-[#14161F] w-full last:border-b-0 [&:not(:last-child)]:border-b sm:border-l font-semibold border-[#3D425C] hover:bg-[#3D425C] active:bg-[#14161F]"
>
<img src="src/assets/cube.svg" alt="" />
{children}
</HashLink>
);
}
function ChooseLang({ lang }: { lang: 'RU' | 'EN' }) {
const { updateLang, value } = useLang();
return (
<button
onClick={() => updateLang(lang)}
className={
'text-[#ffffff] min-h-[72px] w-full h-full btn-text font-semibold bg-[#14161F] hover:bg-[#3D425C] border active:bg-[#14161F] ' +
(value === lang
? '[border-image:linear-gradient(to_right,#798FFF,#D375FF)_3]'
: 'border-[#3D425C]')
}
>
{lang}
</button>
);
}
function LangToggler({ lang }: { lang: Lang }) {
const [open, setOpen] = useState(false);
const langTogglerRef = useRef<HTMLDivElement>(null);
useOnClickOutside(langTogglerRef, () => setOpen(false));
return (
<div
ref={langTogglerRef}
className="min-w-[101px] mobile:max-[1349px]:hidden hover:bg-[#3D425C] active:bg-[#14161F]"
>
<button
onClick={() => setOpen(prev => !prev)}
className="mx-6 h-full gap-x-1 items-center flex text-[#ffffff] font-semibold btn-text"
>
{lang}
<img src="src/assets/arrow_down.svg" alt="" />
</button>
<motion.div
className="absolute grid grid-cols-2 min-w-[101px]"
onClick={() => setOpen(false)}
initial={{ opacity: 0 }}
animate={{ opacity: +open }}
transition={{ duration: 0.2 }}
>
<ChooseLang lang={'RU'} />
<ChooseLang lang={'EN'} />
</motion.div>
</div>
);
}
+42
View File
@@ -0,0 +1,42 @@
import { Marquee } from '../Main/Marquee';
export function Motivation() {
return (
<header className="text-[#ffffff]">
<div className="lg:px-10 sm:px-6 mobile:px-4 lg:py-28 sm:py-12 mobile:py-14">
<h1 className="desktop-figma:mb-[38px] pb-8 font-medium lg:block mobile:max-lg:hidden h1">
Создаем{' '}
<span
className="bg-text-gradient bg-gradient-to-r from-[#798FFF] to-[#D375FF]"
style={{
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
интерактивные тренажеры
</span>{' '}
для промышленности и образования
</h1>
<h1 className="font-medium lg:hidden mobile:max-lg:block h1">
Интерактивные тренажеры{' '}
<span
className="bg-text-gradient bg-gradient-to-r from-[#798FFF] to-[#D375FF]"
style={{
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
для обучения сотрудников
</span>
</h1>
<h3 className="max-w-[768px] lg:block mobile:max-lg:hidden h3">
Помогаем сократить затраты на обучение, повысить безопасность и
производительность
</h3>
</div>
<Marquee />
</header>
);
}
-164
View File
@@ -1,164 +0,0 @@
import { motion } from 'framer-motion';
import { PropsWithChildren, useRef, useState } from 'react';
import { NavLink } from '../../ui/NavLink';
import { Link } from 'react-router-dom';
import { Lang, useLang } from '../../store/language';
import { HashLink } from 'react-router-hash-link';
import { useOnClickOutside } from 'usehooks-ts';
import useModalStore from '../../store/modal';
import ModalWithForm from '../Main/ModalWithForm';
import Button from '../../ui/Button';
export function Navbar() {
const [menuOpen, setMenuOpen] = useState(false);
const { value: lang } = useLang();
const setModal = useModalStore(state => state.setModal);
const menuRef = useRef<HTMLDivElement>(null);
const menuBtnRef = useRef<HTMLButtonElement>(null);
useOnClickOutside<HTMLDivElement | HTMLButtonElement>(
[menuRef, menuBtnRef],
() => setMenuOpen(false),
);
return (
<>
<nav className="flex items-stretch justify-between border-b border-[#3D425C] lg:pl-10 mobile:pl-4 lg:min-h-[72px] mobile:min-h-16">
<Link to={'/'} className="flex w-[104px] justify-between items-center">
<img src="src/assets/logo.svg" alt="" />
<img
src="src/assets/text_logo.svg"
alt=""
className="lg:block mobile:hidden"
/>
</Link>
<div className="flex">
<NavLink route="#products">Типы тренажеров</NavLink>
<NavLink route="#trainings">Варианты комплектации</NavLink>
<NavLink route="#projects">Проекты</NavLink>
<NavLink route="#events">События</NavLink>
<Button
onClick={() => setModal(<ModalWithForm />)}
className="rounded-none btn-text font-semibold sm:block mobile:hidden px-10"
>
Оставить заявку
</Button>
<LangToggler lang={lang} />
<button
ref={menuBtnRef}
onClick={() => setMenuOpen(prev => !prev)}
className="px-6 py-5 min-[1350px]:hidden mobile:block border-[#3D425C] mobile:max-sm:border-l"
>
<img
src={`src/assets/${menuOpen ? 'cross' : 'burger'}.svg`}
alt=""
/>
</button>
</div>
</nav>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: +menuOpen }}
transition={{ duration: 0.2 }}
ref={menuRef}
onClick={() => setMenuOpen(false)}
className={
'absolute z-50 w-full min-[1350px]:hidden sm:max-[1350px]:max-w-[340px] right-0 sm:border-l border-b border-[#3D425C]' +
(menuOpen ? ' shadow-[0_0_0_9999px_rgba(0,0,0,.4)]' : '')
}
>
<BurgerLink route="#products">Типы тренажеров</BurgerLink>
<BurgerLink route="#trainings">Варианты комплектации</BurgerLink>
<BurgerLink route="#projects">Проекты</BurgerLink>
<BurgerLink route="#events">События</BurgerLink>
<div className="grid mobile:max-sm:grid-cols-[216px_1fr_1fr] sm:grid-cols-2">
<Button
onClick={() => {
setMenuOpen(false);
setModal(<ModalWithForm />);
}}
width="full"
className="sm:hidden font-semibold btn-text rounded-none"
>
Оставить заявку
</Button>
<ChooseLang lang="RU" />
<ChooseLang lang="EN" />
</div>
</motion.div>
</>
);
}
function BurgerLink({ children, route }: PropsWithChildren<{ route: string }>) {
return (
<HashLink
to={route}
className="flex items-center px-10 py-6 gap-1 text-[#ffffff] btn-text bg-[#14161F] w-full font-semibold border-[#3D425C] border-b hover:bg-[#3D425C] active:bg-[#14161F]"
>
<img src="src/assets/cube.svg" alt="" />
{children}
</HashLink>
);
}
function ChooseLang({
lang,
isBordered = false,
}: {
lang: 'RU' | 'EN';
isBordered?: boolean;
}) {
const { updateLang, value } = useLang();
return (
<div
className={
'min-h-[72px] ' +
(value !== lang
? 'bg-[#3D425C]'
: 'bg-gradient-to-r from-[#798FFF] to-[#D375FF] p-px')
}
>
<button
onClick={() => updateLang(lang)}
className={
'text-[#ffffff] w-full h-full btn-text font-semibold bg-[#14161F] hover:bg-[#3D425C] active:bg-[#14161F] ' +
(isBordered ? 'border border-[#3D425C]' : '')
}
>
{lang}
</button>
</div>
);
}
function LangToggler({ lang }: { lang: Lang }) {
const [open, setOpen] = useState(false);
const langTogglerRef = useRef<HTMLDivElement>(null);
useOnClickOutside(langTogglerRef, () => setOpen(false));
return (
<div
ref={langTogglerRef}
className="min-w-[101px] mobile:max-[1349px]:hidden box-border border-r border-[#3D425C] hover:bg-[#3D425C] active:bg-[#14161F]"
>
<button
onClick={() => setOpen(prev => !prev)}
className="mx-6 h-full gap-x-1 items-center flex text-[#ffffff] font-semibold box-border btn-text"
>
{lang}
<img src="src/assets/arrow_down.svg" alt="" />
</button>
<motion.div
className="absolute grid grid-cols-2 min-w-[101px] box-border"
onClick={() => setOpen(false)}
initial={{ opacity: 0 }}
animate={{ opacity: +open }}
transition={{ duration: 0.2 }}
>
<ChooseLang isBordered lang={'RU'} />
<ChooseLang isBordered lang={'EN'} />
</motion.div>
</div>
);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import useModalStore from '../../store/modal';
import useModalStore from '../../store/modalStore';
export default function ModalContainer() {
const modal = useModalStore(state => state.modal);
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { Close2Icon } from '../icons/Close2Icon';
import useModalStore from '../../store/modal';
import useModalStore from '../../store/modalStore';
import ContactsForm from './ContactsForm';
function ModalWithForm() {
-2
View File
@@ -116,8 +116,6 @@ function Slider({
setSliderOffset(-baseOffset);
}, [order, baseOffset, slide]);
useEffect(() => console.log(sliderOffset), [sliderOffset]);
return (
<div className="flex flex-col lg:mt-4 sm:mt-3 mobile:mt-2">
<div {...handlers}>
+9 -2
View File
@@ -78,7 +78,14 @@ function TrainingsFeature({
</div>
<div className="mobile:max-sm:flex sm:hidden justify-between items-end">
<p className="text-[#52587A] m-text mb-5">{order}</p>
<img src={src} alt="" className="w-[50vw]" />
<div className="flex flex-col items-center">
<img src={src} alt="" className="relative z-30 w-[50vw]" />
<img
src="src/assets/vr_backlight.svg"
className="absolute w-[36vw]"
alt=""
/>
</div>
</div>
<div className="md:flex mobile:max-md:hidden">
<motion.div
@@ -87,7 +94,7 @@ function TrainingsFeature({
transition={{
duration: 0.4,
}}
className="-my-10 mobile:max-lg:hidden flex items-center justify-center "
className="-my-10 mobile:max-lg:hidden flex items-center justify-center"
>
<img
src={src}
+1 -1
View File
@@ -1,7 +1,7 @@
import { PropsWithChildren } from 'react';
import { HashLink } from 'react-router-hash-link';
export function NavLink({
export function Anchor({
children,
route,
className = '',