diff --git a/client/.env b/client/.env new file mode 100644 index 0000000..cd41370 --- /dev/null +++ b/client/.env @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:3000 \ No newline at end of file diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/client/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..1ebe379 --- /dev/null +++ b/client/README.md @@ -0,0 +1,27 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..7e42895 --- /dev/null +++ b/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..35d924f --- /dev/null +++ b/client/package.json @@ -0,0 +1,34 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "framer-motion": "^10.18.0", + "gsap": "^3.12.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.18.0" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react-swc": "^3.3.2", + "autoprefixer": "^10.4.16", + "eslint": "^8.45.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", + "typescript": "^5.0.2", + "vite": "^4.4.5" + } +} diff --git a/client/postcss.config.js b/client/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/client/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/client/public/images/accordion-1.svg b/client/public/images/accordion-1.svg new file mode 100644 index 0000000..40993ff --- /dev/null +++ b/client/public/images/accordion-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/accordion-2.svg b/client/public/images/accordion-2.svg new file mode 100644 index 0000000..2cbc622 --- /dev/null +++ b/client/public/images/accordion-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/accordion-3.svg b/client/public/images/accordion-3.svg new file mode 100644 index 0000000..6ecf1e5 --- /dev/null +++ b/client/public/images/accordion-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/accordion-4.svg b/client/public/images/accordion-4.svg new file mode 100644 index 0000000..5cb7427 --- /dev/null +++ b/client/public/images/accordion-4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/accordion-5.svg b/client/public/images/accordion-5.svg new file mode 100644 index 0000000..46b51d9 --- /dev/null +++ b/client/public/images/accordion-5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/accordion-6.svg b/client/public/images/accordion-6.svg new file mode 100644 index 0000000..dca8e12 --- /dev/null +++ b/client/public/images/accordion-6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/accordions/accordion-ar-1.png b/client/public/images/accordions/accordion-ar-1.png new file mode 100644 index 0000000..d9e0286 Binary files /dev/null and b/client/public/images/accordions/accordion-ar-1.png differ diff --git a/client/public/images/accordions/accordion-ar-2.png b/client/public/images/accordions/accordion-ar-2.png new file mode 100644 index 0000000..51a9b50 Binary files /dev/null and b/client/public/images/accordions/accordion-ar-2.png differ diff --git a/client/public/images/accordions/accordion-ar-3.png b/client/public/images/accordions/accordion-ar-3.png new file mode 100644 index 0000000..2d02a9f Binary files /dev/null and b/client/public/images/accordions/accordion-ar-3.png differ diff --git a/client/public/images/accordions/accordion-kinnectRealSense-1.png b/client/public/images/accordions/accordion-kinnectRealSense-1.png new file mode 100644 index 0000000..a5a2c12 Binary files /dev/null and b/client/public/images/accordions/accordion-kinnectRealSense-1.png differ diff --git a/client/public/images/accordions/accordion-kinnectRealSense-2.png b/client/public/images/accordions/accordion-kinnectRealSense-2.png new file mode 100644 index 0000000..a709d5c Binary files /dev/null and b/client/public/images/accordions/accordion-kinnectRealSense-2.png differ diff --git a/client/public/images/accordions/accordion-kinnectRealSense-3.png b/client/public/images/accordions/accordion-kinnectRealSense-3.png new file mode 100644 index 0000000..792d76b Binary files /dev/null and b/client/public/images/accordions/accordion-kinnectRealSense-3.png differ diff --git a/client/public/images/accordions/accordion-mobileApp-1.png b/client/public/images/accordions/accordion-mobileApp-1.png new file mode 100644 index 0000000..5957df6 Binary files /dev/null and b/client/public/images/accordions/accordion-mobileApp-1.png differ diff --git a/client/public/images/accordions/accordion-mobileApp-2.png b/client/public/images/accordions/accordion-mobileApp-2.png new file mode 100644 index 0000000..c32df66 Binary files /dev/null and b/client/public/images/accordions/accordion-mobileApp-2.png differ diff --git a/client/public/images/accordions/accordion-mobileApp-3.png b/client/public/images/accordions/accordion-mobileApp-3.png new file mode 100644 index 0000000..88af8e0 Binary files /dev/null and b/client/public/images/accordions/accordion-mobileApp-3.png differ diff --git a/client/public/images/accordions/accordion-touchScreen-1.png b/client/public/images/accordions/accordion-touchScreen-1.png new file mode 100644 index 0000000..281678c Binary files /dev/null and b/client/public/images/accordions/accordion-touchScreen-1.png differ diff --git a/client/public/images/accordions/accordion-touchScreen-2.png b/client/public/images/accordions/accordion-touchScreen-2.png new file mode 100644 index 0000000..8b29945 Binary files /dev/null and b/client/public/images/accordions/accordion-touchScreen-2.png differ diff --git a/client/public/images/accordions/accordion-touchScreen-3.png b/client/public/images/accordions/accordion-touchScreen-3.png new file mode 100644 index 0000000..3afa30b Binary files /dev/null and b/client/public/images/accordions/accordion-touchScreen-3.png differ diff --git a/client/public/images/accordions/accordion-vr-1.png b/client/public/images/accordions/accordion-vr-1.png new file mode 100644 index 0000000..5c24ba1 Binary files /dev/null and b/client/public/images/accordions/accordion-vr-1.png differ diff --git a/client/public/images/accordions/accordion-vr-2.png b/client/public/images/accordions/accordion-vr-2.png new file mode 100644 index 0000000..efca62a Binary files /dev/null and b/client/public/images/accordions/accordion-vr-2.png differ diff --git a/client/public/images/accordions/accordion-vr-3.png b/client/public/images/accordions/accordion-vr-3.png new file mode 100644 index 0000000..f6c4678 Binary files /dev/null and b/client/public/images/accordions/accordion-vr-3.png differ diff --git a/client/public/images/accordions/accordion-webGl-1.png b/client/public/images/accordions/accordion-webGl-1.png new file mode 100644 index 0000000..1890e5c Binary files /dev/null and b/client/public/images/accordions/accordion-webGl-1.png differ diff --git a/client/public/images/accordions/accordion-webGl-2.png b/client/public/images/accordions/accordion-webGl-2.png new file mode 100644 index 0000000..172c468 Binary files /dev/null and b/client/public/images/accordions/accordion-webGl-2.png differ diff --git a/client/public/images/accordions/accordion-webGl-3.png b/client/public/images/accordions/accordion-webGl-3.png new file mode 100644 index 0000000..d3b9dea Binary files /dev/null and b/client/public/images/accordions/accordion-webGl-3.png differ diff --git a/client/public/images/directions/direction-1.jpg b/client/public/images/directions/direction-1.jpg new file mode 100644 index 0000000..3776770 Binary files /dev/null and b/client/public/images/directions/direction-1.jpg differ diff --git a/client/public/images/directions/direction-2.jpg b/client/public/images/directions/direction-2.jpg new file mode 100644 index 0000000..fa9c9b4 Binary files /dev/null and b/client/public/images/directions/direction-2.jpg differ diff --git a/client/public/images/directions/direction-3.jpg b/client/public/images/directions/direction-3.jpg new file mode 100644 index 0000000..ec35291 Binary files /dev/null and b/client/public/images/directions/direction-3.jpg differ diff --git a/client/public/images/directions/direction-4.jpg b/client/public/images/directions/direction-4.jpg new file mode 100644 index 0000000..f85e9c9 Binary files /dev/null and b/client/public/images/directions/direction-4.jpg differ diff --git a/client/public/images/directions/direction-5.jpg b/client/public/images/directions/direction-5.jpg new file mode 100644 index 0000000..fb66f40 Binary files /dev/null and b/client/public/images/directions/direction-5.jpg differ diff --git a/client/public/images/footer-facebook.svg b/client/public/images/footer-facebook.svg new file mode 100644 index 0000000..f99e3cf --- /dev/null +++ b/client/public/images/footer-facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/footer-instagram.svg b/client/public/images/footer-instagram.svg new file mode 100644 index 0000000..2055596 --- /dev/null +++ b/client/public/images/footer-instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/footer-telegram.png b/client/public/images/footer-telegram.png new file mode 100644 index 0000000..7b390a4 Binary files /dev/null and b/client/public/images/footer-telegram.png differ diff --git a/client/public/images/footer-vk.svg b/client/public/images/footer-vk.svg new file mode 100644 index 0000000..23ccfcc --- /dev/null +++ b/client/public/images/footer-vk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/footer-youtube.svg b/client/public/images/footer-youtube.svg new file mode 100644 index 0000000..328728d --- /dev/null +++ b/client/public/images/footer-youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/graffTechLove.png b/client/public/images/graffTechLove.png new file mode 100644 index 0000000..aeb03e4 Binary files /dev/null and b/client/public/images/graffTechLove.png differ diff --git a/client/public/images/services-1.png b/client/public/images/services-1.png new file mode 100644 index 0000000..21cdaca Binary files /dev/null and b/client/public/images/services-1.png differ diff --git a/client/public/images/services-10.png b/client/public/images/services-10.png new file mode 100644 index 0000000..be8eab2 Binary files /dev/null and b/client/public/images/services-10.png differ diff --git a/client/public/images/services-11.png b/client/public/images/services-11.png new file mode 100644 index 0000000..0adb1d8 Binary files /dev/null and b/client/public/images/services-11.png differ diff --git a/client/public/images/services-2.png b/client/public/images/services-2.png new file mode 100644 index 0000000..ca7a179 Binary files /dev/null and b/client/public/images/services-2.png differ diff --git a/client/public/images/services-3.png b/client/public/images/services-3.png new file mode 100644 index 0000000..b4d8e29 Binary files /dev/null and b/client/public/images/services-3.png differ diff --git a/client/public/images/services-4.png b/client/public/images/services-4.png new file mode 100644 index 0000000..449fe43 Binary files /dev/null and b/client/public/images/services-4.png differ diff --git a/client/public/images/services-5.png b/client/public/images/services-5.png new file mode 100644 index 0000000..f2b166b Binary files /dev/null and b/client/public/images/services-5.png differ diff --git a/client/public/images/services-6.png b/client/public/images/services-6.png new file mode 100644 index 0000000..2dbd58e Binary files /dev/null and b/client/public/images/services-6.png differ diff --git a/client/public/images/services-7.png b/client/public/images/services-7.png new file mode 100644 index 0000000..db18aa6 Binary files /dev/null and b/client/public/images/services-7.png differ diff --git a/client/public/images/services-8.png b/client/public/images/services-8.png new file mode 100644 index 0000000..4e76e8f Binary files /dev/null and b/client/public/images/services-8.png differ diff --git a/client/public/images/services-9.png b/client/public/images/services-9.png new file mode 100644 index 0000000..3e4d751 Binary files /dev/null and b/client/public/images/services-9.png differ diff --git a/client/public/images/slides/first-slide-1.png b/client/public/images/slides/first-slide-1.png new file mode 100644 index 0000000..d0b0192 Binary files /dev/null and b/client/public/images/slides/first-slide-1.png differ diff --git a/client/public/images/slides/first-slide-2.png b/client/public/images/slides/first-slide-2.png new file mode 100644 index 0000000..890fe95 Binary files /dev/null and b/client/public/images/slides/first-slide-2.png differ diff --git a/client/public/images/slides/second-slide-1.svg b/client/public/images/slides/second-slide-1.svg new file mode 100644 index 0000000..76a5653 --- /dev/null +++ b/client/public/images/slides/second-slide-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/slides/second-slide-2.svg b/client/public/images/slides/second-slide-2.svg new file mode 100644 index 0000000..b557cd3 --- /dev/null +++ b/client/public/images/slides/second-slide-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/slides/third-slide-1.svg b/client/public/images/slides/third-slide-1.svg new file mode 100644 index 0000000..3404fff --- /dev/null +++ b/client/public/images/slides/third-slide-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/slides/third-slide-2.svg b/client/public/images/slides/third-slide-2.svg new file mode 100644 index 0000000..005f003 --- /dev/null +++ b/client/public/images/slides/third-slide-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/vite.svg b/client/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/client/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/DirectionsPage/DirectionImage.tsx b/client/src/components/DirectionsPage/DirectionImage.tsx new file mode 100644 index 0000000..4a5b5c2 --- /dev/null +++ b/client/src/components/DirectionsPage/DirectionImage.tsx @@ -0,0 +1,28 @@ +import ArrowIcon from "../../icons/ArrowIcon"; + +type DirectionImageProps = { + title: string; + description: string; + image: string; +}; + +const DirectionImage = ({ title, description, image }: DirectionImageProps) => { + return ( +
+ +
+
{title}
+ +
+
+ {description} +
+
+ ); +}; + +export default DirectionImage; diff --git a/client/src/components/DirectionsPage/DirectionImageContainer.tsx b/client/src/components/DirectionsPage/DirectionImageContainer.tsx new file mode 100644 index 0000000..07751fd --- /dev/null +++ b/client/src/components/DirectionsPage/DirectionImageContainer.tsx @@ -0,0 +1,52 @@ +import DirectionImage from "./DirectionImage"; +import { DirectionImageType } from "../../types"; + +const directionImages: DirectionImageType[] = [ + { + id: 1, + image: "/images/directions/direction-1.jpg", + title: "НЕДВИЖИМОСТЬ", + desctiption: "Покажет квартиры на этапе строительства", + }, + { + id: 2, + image: "/images/directions/direction-2.jpg", + title: "ТРЕНАЖЁРЫ", + desctiption: "Обучит работе с оборудованием", + }, + { + id: 3, + image: "/images/directions/direction-3.jpg", + title: "ТЕХНИКА", + desctiption: "Презентует технику в 3D", + }, + { + id: 4, + image: "/images/directions/direction-4.jpg", + title: "ВЫСТАВКИ", + desctiption: "Вовлечёт посетителей стенда", + }, + { + id: 5, + image: "/images/directions/direction-5.jpg", + title: "ОНЛАЙН МЕРОПРИЯТИЯ", + desctiption: "Соберёт аудиторию со всего мира", + }, +]; + +const DirectionImageContainer = () => { + return ( +
+ {directionImages.map((dir) => ( + + ))} +
+ ); +}; + +export default DirectionImageContainer; diff --git a/client/src/components/Footer/Footer.tsx b/client/src/components/Footer/Footer.tsx new file mode 100644 index 0000000..b2d5308 --- /dev/null +++ b/client/src/components/Footer/Footer.tsx @@ -0,0 +1,15 @@ +import PrivacyPolicy from "./PrivacyPolicy"; +import Tabs from "../Tabs"; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/client/src/components/Footer/FooterSocials.tsx b/client/src/components/Footer/FooterSocials.tsx new file mode 100644 index 0000000..2aebd62 --- /dev/null +++ b/client/src/components/Footer/FooterSocials.tsx @@ -0,0 +1,23 @@ +const FooterSocials = () => { + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ); +}; + +export default FooterSocials; diff --git a/client/src/components/Footer/PrivacyPolicy.tsx b/client/src/components/Footer/PrivacyPolicy.tsx new file mode 100644 index 0000000..987186f --- /dev/null +++ b/client/src/components/Footer/PrivacyPolicy.tsx @@ -0,0 +1,30 @@ +import LogoIcon from "../../icons/LogoIcon"; +import FooterSocials from "./FooterSocials"; + +const PrivacyPolicy = () => { + return ( +
+
+ +
+ Политика конфиденциальности
+ copyright © Graff Interactive +
+
+
+
8 (800) 770 00 67
+
Russia
+
info@graff.tech
+
+
+ +
+
Разработка сайта:
+
ТитанСофт
+
+
+
+ ); +}; + +export default PrivacyPolicy; diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx new file mode 100644 index 0000000..9b839de --- /dev/null +++ b/client/src/components/Header/Header.tsx @@ -0,0 +1,26 @@ +import LogoIcon from "../../icons/LogoIcon"; +import SocialLinks from "./SocialLinks"; +import LanguageSwitcher from "./LanguageSwitcher"; +import Tabs from "../Tabs"; + +const Header = (): JSX.Element => { + return ( + <> +
+
+ +
+
+ +
+ +
+ + +
+
+ + ); +}; + +export default Header; diff --git a/client/src/components/Header/LanguageSwitcher.tsx b/client/src/components/Header/LanguageSwitcher.tsx new file mode 100644 index 0000000..365a276 --- /dev/null +++ b/client/src/components/Header/LanguageSwitcher.tsx @@ -0,0 +1,30 @@ +import { useState } from "react"; + +const languages: Language[] = ["Ru", "Eng"]; + +type Language = "Ru" | "Eng"; + +const LanguageSwitcher = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [language, setLanguage] = useState("Ru"); + + const onLangClick = (lang: Language) => { + return () => setLanguage(lang); + }; + + return ( +
+ {languages.map((lang) => ( + + ))} +
+ ); +}; + +export default LanguageSwitcher; diff --git a/client/src/components/Header/SocialLinks.tsx b/client/src/components/Header/SocialLinks.tsx new file mode 100644 index 0000000..525d9b4 --- /dev/null +++ b/client/src/components/Header/SocialLinks.tsx @@ -0,0 +1,17 @@ +import VkIcon from "../../icons/VkIcon"; +import FacebookIcon from "../../icons/FacebookIcon"; +import InstagramIcon from "../../icons/InstagramIcon"; +import YoutubeIcon from "../../icons/YoutubeIcon"; + +const SocialLinks = () => { + return ( +
+ + + + +
+ ); +}; + +export default SocialLinks; diff --git a/client/src/components/HeaderTitle.tsx b/client/src/components/HeaderTitle.tsx new file mode 100644 index 0000000..0603a07 --- /dev/null +++ b/client/src/components/HeaderTitle.tsx @@ -0,0 +1,15 @@ +type HeaderTitleProps = { + title: string; + description?: string; +}; + +const HeaderTitle = ({ title, description }: HeaderTitleProps) => { + return ( +
+

{title}

+

{description}

+
+ ); +}; + +export default HeaderTitle; diff --git a/client/src/components/MainPage/AboutUs.tsx b/client/src/components/MainPage/AboutUs.tsx new file mode 100644 index 0000000..d8075d7 --- /dev/null +++ b/client/src/components/MainPage/AboutUs.tsx @@ -0,0 +1,70 @@ +const AboutUs = () => { + return ( +
+
+
+

+ К нам приходят +

+

+ Когда нужно продать то, что еще не построено, презентовать то, что + еще не создано, разработать технологичные и эффективные визуальные + решения для продвижения бизнеса. +

+
+ img +
+ +
+ ); +}; + +export default AboutUs; diff --git a/client/src/components/MainPage/LastProjects.tsx b/client/src/components/MainPage/LastProjects.tsx new file mode 100644 index 0000000..fdc457f --- /dev/null +++ b/client/src/components/MainPage/LastProjects.tsx @@ -0,0 +1,63 @@ +const LastProjects = () => { + return ( +
+

+ Последние проекты +

+
+
+
+ + + +
+
+ + + +
+
+
+ img + + +
+ +
+
+
+ ); +}; + +export default LastProjects; diff --git a/client/src/components/MainPage/OftenQuestion.tsx b/client/src/components/MainPage/OftenQuestion.tsx new file mode 100644 index 0000000..8660925 --- /dev/null +++ b/client/src/components/MainPage/OftenQuestion.tsx @@ -0,0 +1,34 @@ +import Question from "./Question"; +import SubmitApplication from "../SubmitApplication"; + +const Feedback = () => { + return ( +
+
+
+

+ Частые +
вопросы +

+
    + + + +
+
+ +
+
+ ); +}; + +export default Feedback; diff --git a/client/src/components/MainPage/Question.tsx b/client/src/components/MainPage/Question.tsx new file mode 100644 index 0000000..f91b96c --- /dev/null +++ b/client/src/components/MainPage/Question.tsx @@ -0,0 +1,31 @@ +import { useState } from "react"; +import CrossCircleIcon from "../../icons/CrossCircleIcon"; + +type QuestionProps = { + title: string; + description: string; +}; + +const Question = ({ title, description }: QuestionProps) => { + const [isOpen, setIsOpen] = useState(false); + + const handleOnQuestionClick = () => { + setIsOpen((prev) => !prev); + }; + return ( +
  • +
    +
    {title}
    + +
    +
    {description}
    +
  • + ); +}; + +export default Question; diff --git a/client/src/components/MainPage/Services.tsx b/client/src/components/MainPage/Services.tsx new file mode 100644 index 0000000..6adf01a --- /dev/null +++ b/client/src/components/MainPage/Services.tsx @@ -0,0 +1,207 @@ +import LongArrowIcon from "../../icons/LongArrowIcon"; +import ShadowIcon from "../../icons/ShadowIcon"; + +const images = [ + { id: "services-2", url: "/images/services-2.png" }, + { id: "services-3", url: "/images/services-3.png" }, + { id: "services-4", url: "/images/services-4.png" }, + { id: "services-5", url: "/images/services-5.png" }, + { id: "services-6", url: "/images/services-6.png" }, + { id: "services-7", url: "/images/services-7.png" }, + { id: "services-8", url: "/images/services-8.png" }, + { id: "services-9", url: "/images/services-9.png" }, + { id: "services-10", url: "/images/services-10.png" }, + { id: "services-11", url: "/images/services-11.png" }, + { id: "services-1", url: "/images/services-1.png" }, +]; + +const Services = () => { + const handleOnMouseEnter = (id: string) => { + return () => { + const image = document.getElementById(`services-${id}`); + if (image) { + image.style.opacity = "100%"; + } + }; + }; + + const handleOnMouseLeave = (id: string) => { + return () => { + const image = document.getElementById(`services-${id}`); + if (image) { + image.style.opacity = "0%"; + } + }; + }; + + return ( +
    +
    +
    +
    + Что мы делаем + +
    +
    + {images.map((img) => ( + картинка + ))} +
    +
    +
    +
    По технологиям
    + + + + + + +
    +
    +
    По направлениям
    + + + + + +
    +
    +
    + ); +}; + +export default Services; diff --git a/client/src/components/MainPage/Slider.tsx b/client/src/components/MainPage/Slider.tsx new file mode 100644 index 0000000..95b9a83 --- /dev/null +++ b/client/src/components/MainPage/Slider.tsx @@ -0,0 +1,63 @@ +import { useState } from "react"; +import FirstSlide from "./slides/FirstSlide"; +import SecondSlide from "./slides/SecondSlide"; +import ThirdSlide from "./slides/ThirdSlide"; + +const slides = [ + { element: , id: 1 }, + { element: , id: 2 }, + { element: , id: 3 }, +]; + +const MainPageSlider = () => { + const [offset, setOffset] = useState(-0); + + const handleRightArrowClick = () => { + setOffset((currentOffset) => { + if (currentOffset === -1 * slides.length + 1) { + return 0; + } + + return currentOffset - 1; + }); + }; + + const handleLeftArrowClick = () => { + setOffset((currentOffset) => { + if (currentOffset === 0) { + return -1 * slides.length + 1; + } + + return currentOffset + 1; + }); + }; + return ( +
    +
    +
    +
    + {slides.map((slide) => ( +
    {slide.element}
    + ))} +
    +
    + +
    + + +
    +
    +
    + ); +}; + +export default MainPageSlider; diff --git a/client/src/components/MainPage/slides/FirstSlide.tsx b/client/src/components/MainPage/slides/FirstSlide.tsx new file mode 100644 index 0000000..f7aba1c --- /dev/null +++ b/client/src/components/MainPage/slides/FirstSlide.tsx @@ -0,0 +1,73 @@ +import { useRef } from "react"; + +const FirstSlide = () => { + const firstImageRef = useRef(null); + const secondImageRef = useRef(null); + + // useEffect(() => { + // if (firstImageRef.current) { + // console.log("imageRef.current", firstImageRef.current); + // gsap.fromTo( + // firstImageRef.current, + // { transform: "scale(2.0)" }, + // { + // transform: "scale(1.6)", + // duration: 1.5, + // scrollTrigger: { + // trigger: firstImageRef.current, + // start: "10% bottom", + // }, + // } + // ); + // gsap.fromTo( + // secondImageRef.current, + // { transform: "scale(2.0)", left: "5%" }, + // { + // transform: "scale(1.6)", + // left: "-5%", + // duration: 2.2, + // scrollTrigger: { + // trigger: firstImageRef.current, + // start: "10% bottom", + // }, + // } + // ); + // } + // }, []); + return ( +
    +

    + Технологии расширенной +
    реальности для вашего бизнеса +

    + + {/* */} + image + {/* */} + image +

    + Мы приглашаем вас в мир +
    удивительного будущего с
    технологиями виртуальной
    и + смешанной реальности +

    +
    + ); +}; + +export default FirstSlide; diff --git a/client/src/components/MainPage/slides/SecondSlide.tsx b/client/src/components/MainPage/slides/SecondSlide.tsx new file mode 100644 index 0000000..9ee6f9e --- /dev/null +++ b/client/src/components/MainPage/slides/SecondSlide.tsx @@ -0,0 +1,41 @@ +import { useEffect, useRef } from "react"; +import { gsap } from "gsap"; + +const SecondSlide = () => { + const firstImageRef = useRef(null); + useEffect(() => { + if (firstImageRef.current) + gsap.to(firstImageRef.current, { + left: "151%", + top: "18%", + duration: 1.5, + }); + }, []); + + return ( +
    +

    + Решаем задачи с помощью +
    интерактивных решений +

    + image + image +

    + Мы презентуем ваш бизнес с помощью инновационных технологий + визуализации: 3D, виртуальной(VR) и дополненной (AR)реальности, 360 + фото, видео, аэросъемки. +

    +
    + ); +}; + +export default SecondSlide; diff --git a/client/src/components/MainPage/slides/ThirdSlide.tsx b/client/src/components/MainPage/slides/ThirdSlide.tsx new file mode 100644 index 0000000..54f83f6 --- /dev/null +++ b/client/src/components/MainPage/slides/ThirdSlide.tsx @@ -0,0 +1,43 @@ +import { useRef, useEffect } from "react"; +import { gsap } from "gsap"; + +const ThirdSlide = () => { + const firstImageRef = useRef(null); + + useEffect(() => { + if (firstImageRef.current) + gsap.fromTo( + firstImageRef.current, + { + transform: "scale(1.15)", + }, + { transform: "scale(1)", duration: 1.5 } + ); + }, []); + return ( +
    +

    + Комплексный подход к каждому +
    проекту +

    + image + image +

    + Анализируем потенциальную аудиторию и исходные данные. Разрабатываем + решения от концепции до идеального проекта. Создаем и реализуем ваши + самые смелые идеи и задумки. +

    +
    + ); +}; + +export default ThirdSlide; diff --git a/client/src/components/ScrollToTopButton.tsx b/client/src/components/ScrollToTopButton.tsx new file mode 100644 index 0000000..c0a27d0 --- /dev/null +++ b/client/src/components/ScrollToTopButton.tsx @@ -0,0 +1,68 @@ +import { useEffect, useRef, useState } from "react"; +import ScrollToTopIcon from "../icons/ScrollToTopIcon"; + +const ScrollToTopButton = () => { + const [isVisible, setIsVisible] = useState(true); + const buttonRef = useRef(null); + + const handleOnScroll = () => { + if (window.scrollY > 1000) { + setIsVisible(true); + } else { + setIsVisible(false); + } + }; + + const handleOnClick = () => { + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + }; + + useEffect(() => { + window.addEventListener("scroll", handleOnScroll); + + return () => { + window.removeEventListener("scroll", handleOnScroll); + }; + }, []); + + return ( + + ); +}; + +// const contentRef = useRef(null); +// const divRef = useRef(null); + +// function handleClick() { +// onClick(); + +// setTimeout(() => { +// window.scrollTo({ +// top: divRef.current?.offsetTop, +// behavior: "smooth", +// }); +// }, 200); +// } + +// return ( +//
    +// +//
    { + return ( +
    +
    +

    + {title} +

    +
    +

    + Оставьте заявку и наш менеджер свяжется с вами. Расскажем о том, как + мы сможем решить ваши задачи, дадим оценку стоимости и сроков +

    +
    +
    + Оставить заявку +
    + +
    +
    + graff +
    +
    + ); +}; + +export default SubmitApplication; diff --git a/client/src/components/Tab.tsx b/client/src/components/Tab.tsx new file mode 100644 index 0000000..f54f0a1 --- /dev/null +++ b/client/src/components/Tab.tsx @@ -0,0 +1,15 @@ +interface TabProps { + value: string; + onClick: () => void; + className?: string; +} + +const Tab = ({ value, onClick, className }: TabProps): JSX.Element => { + return ( + + ); +}; + +export default Tab; diff --git a/client/src/components/Tabs.tsx b/client/src/components/Tabs.tsx new file mode 100644 index 0000000..f37cbbf --- /dev/null +++ b/client/src/components/Tabs.tsx @@ -0,0 +1,121 @@ +import { useNavigate, useLocation } from "react-router-dom"; +import Tab from "./Tab"; +import { useEffect, useState } from "react"; + +type Tab = { + link: string; + label: string; + id: number; +}; + +const tabs: Tab[] = [ + { link: "/technology", label: "Технологии", id: 1 }, + { link: "/directions", label: "Направления", id: 2 }, + { link: "/gallery", label: "Проекты", id: 3 }, + { link: "/clients", label: "Наши клиекнты", id: 4 }, + { link: "/blog", label: "Блог", id: 5 }, + { link: "/contacts", label: "Контакты", id: 6 }, + { link: "/vacancy", label: "Вакансии", id: 7 }, + { link: "/about", label: "О компании", id: 8 }, +]; + +type TabsProps = { + isHeader?: boolean; +}; + +const Tabs = ({ isHeader = false }: TabsProps) => { + const navigate = useNavigate(); + const location = useLocation(); + + const handleOnNavigateClick = (tab: string) => { + return () => { + navigate(tab); + }; + }; + + return ( + <> + {isHeader ? ( + + ) : ( + + )} + + ); +}; + +type HeaderTabsProps = { + handleOnNavigateClick: (tab: string) => () => void; + pathname: string; +}; + +const HeaderTabs = ({ handleOnNavigateClick, pathname }: HeaderTabsProps) => { + const [currentTab, setCurrentTab] = useState(undefined); + + useEffect(() => { + setCurrentTab(tabs.find((tab) => tab.link === pathname)); + }, [pathname]); + + return ( +
    + +
    +
    Главная
    {" "} +
    / {currentTab?.label}
    +
    +
    + ); +}; + +type FooterTabsProps = { + handleOnNavigateClick: (tab: string) => () => void; + pathname: string; +}; + +const FooterTabs = ({ handleOnNavigateClick, pathname }: FooterTabsProps) => ( +
    + +
    +); + +export default Tabs; diff --git a/client/src/components/TechnologyPage/Accordion.tsx b/client/src/components/TechnologyPage/Accordion.tsx new file mode 100644 index 0000000..5a3049a --- /dev/null +++ b/client/src/components/TechnologyPage/Accordion.tsx @@ -0,0 +1,83 @@ +import { useRef } from "react"; +import { AccordionType } from "../../types"; + +type AccordionProps = { + accordion: AccordionType; + isSelected?: boolean; + onClick: () => void; +}; + +const Accordion = ({ + accordion, + onClick, + isSelected = false, +}: AccordionProps) => { + const { title, description, icon, content } = accordion; + const divRef = useRef(null); + const contentRef = useRef(null); + + const handleOnClick = () => { + onClick(); + + const timeout = setTimeout(() => { + window.scrollTo({ + top: contentRef.current?.offsetTop, + behavior: "smooth", + }); + + clearTimeout(timeout); + }, 500); + }; + + return ( +
    +
    +
    +
    +
    {title}
    +
    {description}
    +
    + {title} +
    +
    +
    {content}
    +
    + ); +}; + +export default Accordion; + +// const contentRef = useRef(null); +// const divRef = useRef(null); + +// function handleClick() { +// onClick(); + +// setTimeout(() => { +// window.scrollTo({ +// top: divRef.current?.offsetTop, +// behavior: "smooth", +// }); +// }, 200); +// } + +// return ( +//
    +// +//
    , + icon: "/images/accordion-1.svg", + id: 1, + }, + { + title: "AR", + description: "Дополненная реальность", + content: , + icon: "/images/accordion-2.svg", + id: 2, + }, + { + title: "Mobile App", + description: "Мобильные приложения", + content: , + icon: "/images/accordion-3.svg", + id: 3, + }, + { + title: "Touch-screen", + description: "3D графика для презентаций", + content: , + icon: "/images/accordion-4.svg", + id: 4, + }, + { + title: "WEB GL", + description: "3D для сайта и мобильных приложений", + content: , + icon: "/images/accordion-5.svg", + id: 5, + }, + { + title: "KINECT REAL SENSE", + description: "Презентации с бесконтактным управлением", + content: , + icon: "/images/accordion-6.svg", + id: 6, + }, +]; + +const AccordionsContainer = () => { + const [selected, setSelected] = useState(null); + + const handleOnClick = (accordion: AccordionType) => { + if (selected?.id === accordion.id) { + setSelected(null); + } else { + setSelected(accordion); + } + }; + + return ( +
    + {accordions.map((acc) => ( + handleOnClick(acc)} + /> + ))} +
    + ); +}; + +export default AccordionsContainer; diff --git a/client/src/components/TechnologyPage/TechnologyAdvantages.tsx b/client/src/components/TechnologyPage/TechnologyAdvantages.tsx new file mode 100644 index 0000000..9c4f977 --- /dev/null +++ b/client/src/components/TechnologyPage/TechnologyAdvantages.tsx @@ -0,0 +1,27 @@ +import { AdvantageType } from "../../types"; + +type TechnologyAdvantagesProps = { + title: string; + advantages: AdvantageType[]; +}; + +const TechnologyAdvantages = ({ + title, + advantages, +}: TechnologyAdvantagesProps) => { + return ( +
    +

    {title}

    +
    + {advantages.map((adv) => ( +
    +
    {adv.title}
    +
    {adv.description}
    +
    + ))} +
    +
    + ); +}; + +export default TechnologyAdvantages; diff --git a/client/src/components/TechnologyPage/accordions/AR.tsx b/client/src/components/TechnologyPage/accordions/AR.tsx new file mode 100644 index 0000000..79d7426 --- /dev/null +++ b/client/src/components/TechnologyPage/accordions/AR.tsx @@ -0,0 +1,93 @@ +import TechnologyCategoryIcon from "../../../icons/TechnologyCategoryIcon"; +import { AdvantageType } from "../../../types"; +import TechnologyAdvantages from "../TechnologyAdvantages"; + +const ADVANTAGES: AdvantageType[] = [ + { + title: "01", + description: "AR добавляет элементы цифрового мира в реальный.", + }, + { + title: "02", + description: "Дает возможность кастомизировать и примерить товар", + }, + { + title: "03", + description: 'AR позволяет "оживить" статичный экспонат', + }, + { + title: "04", + description: "Повышает эффективность обучения", + }, + { + title: "05", + description: "Наглядно демонстрирует преимущества и работу продуктов", + }, +]; + +const AR = () => { + return ( +
    +
    +

    + Сегодня покупатель недвижимости платит не просто за квадратные метры – + он платит за уникальные характеристики дома – инфраструктуру, удобный + и комфортный двор, дизайн подъездов и холлов, вид из окон. Нужно + показать ему все преимущества жилого комплекса на любой стадии + строительства еще в офисе продаж. +

    +
    +
    +
    +
    +
    +
    + Как это +
    работает +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ); +}; + +export default AR; diff --git a/client/src/components/TechnologyPage/accordions/KinnectRealSense.tsx b/client/src/components/TechnologyPage/accordions/KinnectRealSense.tsx new file mode 100644 index 0000000..ba304ab --- /dev/null +++ b/client/src/components/TechnologyPage/accordions/KinnectRealSense.tsx @@ -0,0 +1,96 @@ +import TechnologyCategoryIcon from "../../../icons/TechnologyCategoryIcon"; +import { AdvantageType } from "../../../types"; +import TechnologyAdvantages from "../TechnologyAdvantages"; + +const ADVANTAGES: AdvantageType[] = [ + { + title: "01", + description: + "Помогает реализовать бесконтактный метод взаимодействия с контентом", + }, + { + title: "02", + description: + "Дает возможность работать на малых расстояниях порядка метра и только внутри помещений", + }, + { + title: "03", + description: "Возможность сканирования предметов", + }, + { + title: "04", + description: "Приложения интуитивно понятны", + }, + { + title: "05", + description: + "Позволяет манипулировать фото изображениями с использованием данных о глубине", + }, +]; + +const KinnectRealSense = () => { + return ( +
    +
    +

    + KINECT REAL SENSE Technology - это продукт с технологиями + глубины и слежения, предназначенных для того, чтобы дать машинам и + устройствам возможности восприятия глубины. Технологии, используются в + автономных беспилотных летательных аппаратах, роботах, AR/VR, + устройствах умного дома и многих других продуктах широкого рынка. +

    +
    +
    +
    +
    +
    +
    + Как это +
    работает +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ); +}; + +export default KinnectRealSense; diff --git a/client/src/components/TechnologyPage/accordions/MobileApp.tsx b/client/src/components/TechnologyPage/accordions/MobileApp.tsx new file mode 100644 index 0000000..bc48c12 --- /dev/null +++ b/client/src/components/TechnologyPage/accordions/MobileApp.tsx @@ -0,0 +1,94 @@ +import TechnologyCategoryIcon from "../../../icons/TechnologyCategoryIcon"; +import { AdvantageType } from "../../../types"; +import TechnologyAdvantages from "../TechnologyAdvantages"; + +const ADVANTAGES: AdvantageType[] = [ + { + title: "01", + description: + "Работать на мобильном устройстве через сенсорный экран или кнопки", + }, + { + title: "02", + description: + "Показать любой вид оборудования, техники и инфраструктурных объектов без физических моделей", + }, + { + title: "03", + description: "Выполняет функции даже в фоновом режиме", + }, + { + title: "04", + description: "Хранить персональные данные пользователя", + }, + { + title: "05", + description: "Возможность задействовать все ресурсы", + }, +]; + +const MobileApp = () => { + return ( +
    +
    +

    + Показать продукцию потенциальному заказчику, даже на этапе разработки, + можно с помощью современных интерактивных технологий — виртуальной и + дополненной реальности, мультимедийных сенсорных панелей и мобильных + устройств. +

    +
    +
    +
    +
    +
    +
    + Как это +
    работает +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ); +}; + +export default MobileApp; diff --git a/client/src/components/TechnologyPage/accordions/TouchScreen.tsx b/client/src/components/TechnologyPage/accordions/TouchScreen.tsx new file mode 100644 index 0000000..5e8ddca --- /dev/null +++ b/client/src/components/TechnologyPage/accordions/TouchScreen.tsx @@ -0,0 +1,98 @@ +import TechnologyCategoryIcon from "../../../icons/TechnologyCategoryIcon"; +import { AdvantageType } from "../../../types"; +import TechnologyAdvantages from "../TechnologyAdvantages"; + +const ADVANTAGES: AdvantageType[] = [ + { + title: "01", + description: + "Демонстрирует интерактивные инструкции, навигацию производства", + }, + { + title: "02", + description: + "Применение в сложных промышленных схемах и в качестве оборудования самообслуживания", + }, + { + title: "03", + description: + "Помогает продемонстрировать 3D конфигуратор, систему подбора недвижимости", + }, + { + title: "04", + description: "Позволяет проводить обучение сотрудников с элементами игры", + }, + { + title: "05", + description: + "Управление с сенсорного экрана, это отличная альтернатива VR или другим контроллерам", + }, +]; + +const TouchScreen = () => { + return ( +
    +
    +

    + Сенсорные дисплеи и тач-панели - отличное решение для демонстрации + всех преимуществ продукта в рамках выставки или фестиваля. +
    +
    Презентация на сенсорном дисплее обладает преимуществами + интуитивного управления мобильного приложения и высоким уровнем + графики, недоступным для мобильных устройств. +

    +
    +
    +
    +
    +
    +
    + Как это +
    работает +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ); +}; + +export default TouchScreen; diff --git a/client/src/components/TechnologyPage/accordions/VR.tsx b/client/src/components/TechnologyPage/accordions/VR.tsx new file mode 100644 index 0000000..95abe9b --- /dev/null +++ b/client/src/components/TechnologyPage/accordions/VR.tsx @@ -0,0 +1,93 @@ +import TechnologyCategoryIcon from "../../../icons/TechnologyCategoryIcon"; +import { AdvantageType } from "../../../types"; +import TechnologyAdvantages from "../TechnologyAdvantages"; + +const ADVANTAGES: AdvantageType[] = [ + { + title: "01", + description: "VR позволяет создать быстрый и мощный wow-эффект", + }, + { + title: "02", + description: + "Презентовать товар и услугу, которую в реальности продемонстрировать сложно", + }, + { + title: "03", + description: + "Показать объем пространства, планировки объекта, который может быть еще не построен", + }, + { + title: "04", + description: + "VR в обучении позволяет повысить эффективность подачи материала", + }, + { + title: "05", + description: + "Мы не смотрим на изображение на экране, а непосредственно находимся в виртуальном пространстве", + }, +]; + +const VR = () => { + return ( +
    +
    +

    + Сегодня покупатель недвижимости платит не просто за квадратные метры – + он платит за уникальные характеристики дома – инфраструктуру, удобный + и комфортный двор, дизайн подъездов и холлов, вид из окон. Нужно + показать ему все преимущества жилого комплекса на любой стадии + строительства еще в офисе продаж. +

    +
    +
    +
    +
    +
    +
    + Как это +
    работает +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + ); +}; + +export default VR; diff --git a/client/src/components/TechnologyPage/accordions/WebGl.tsx b/client/src/components/TechnologyPage/accordions/WebGl.tsx new file mode 100644 index 0000000..326a261 --- /dev/null +++ b/client/src/components/TechnologyPage/accordions/WebGl.tsx @@ -0,0 +1,94 @@ +import TechnologyCategoryIcon from "../../../icons/TechnologyCategoryIcon"; +import { AdvantageType } from "../../../types"; +import TechnologyAdvantages from "../TechnologyAdvantages"; + +const ADVANTAGES: AdvantageType[] = [ + { + title: "01", + description: "Выводить 3D графику на экран", + }, + { + title: "02", + description: "Конструировать приложения как веб-страницы", + }, + { + title: "03", + description: + "Успешно отображать одну и туже программу на разных устройствах", + }, + { + title: "04", + description: + "Реализовывать более сложные взаимодействия элементов и отображения динамического контента", + }, + { + title: "05", + description: + "Использовать трехмерную графику для визуализации различной информации", + }, +]; + +const WebGl = () => { + return ( +
    +
    +

    + WebGL — это то, что «оживляет» веб-ресурс, позволяет обойтись без + флеш-плеера, так как она выполняется видеокартой и является «частью» + элемента «canvas» разметки HTML. +

    +
    +
    +
    +
    +
    +
    + Как это +
    работает +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ); +}; + +export default WebGl; diff --git a/client/src/components/Wrapper.tsx b/client/src/components/Wrapper.tsx new file mode 100644 index 0000000..82dc0d8 --- /dev/null +++ b/client/src/components/Wrapper.tsx @@ -0,0 +1,17 @@ +import Header from "./Header/Header"; +import Footer from "./Footer/Footer"; +import { Outlet } from "react-router-dom"; +import ScrollToTopButton from "./ScrollToTopButton"; + +const Wrapper = () => { + return ( + <> +
    + + +