This commit is contained in:
2024-08-29 18:47:58 +05:00
commit a17faa0822
63 changed files with 3293 additions and 0 deletions
+24
View File
@@ -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?
+7
View File
@@ -0,0 +1,7 @@
{
"arrowParens": "avoid",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all"
}
+50
View File
@@ -0,0 +1,50 @@
# 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
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
+28
View File
@@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
{
"name": "graff.event",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^11.3.31",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1",
"usehooks-ts": "^3.1.0"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.41",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+250
View File
@@ -0,0 +1,250 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, noarchive">
<meta name="format-detection" content="telephone=no">
<title>Transfonter demo</title>
<link href="stylesheet.css" rel="stylesheet">
<style>
/*
http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* demo styles */
body {
background: #f0f0f0;
color: #000;
}
.page {
background: #fff;
width: 920px;
margin: 0 auto;
padding: 20px 20px 0 20px;
overflow: hidden;
}
.font-container {
overflow-x: auto;
overflow-y: hidden;
margin-bottom: 40px;
line-height: 1.3;
white-space: nowrap;
padding-bottom: 5px;
}
h1 {
position: relative;
background: #444;
font-size: 32px;
color: #fff;
padding: 10px 20px;
margin: 0 -20px 12px -20px;
}
.letters {
font-size: 25px;
margin-bottom: 20px;
}
.s10:before {
content: '10px';
}
.s11:before {
content: '11px';
}
.s12:before {
content: '12px';
}
.s14:before {
content: '14px';
}
.s18:before {
content: '18px';
}
.s24:before {
content: '24px';
}
.s30:before {
content: '30px';
}
.s36:before {
content: '36px';
}
.s48:before {
content: '48px';
}
.s60:before {
content: '60px';
}
.s72:before {
content: '72px';
}
.s10:before, .s11:before, .s12:before, .s14:before,
.s18:before, .s24:before, .s30:before, .s36:before,
.s48:before, .s60:before, .s72:before {
font-family: Arial, sans-serif;
font-size: 10px;
font-weight: normal;
font-style: normal;
color: #999;
padding-right: 6px;
}
pre {
display: block;
padding: 9px;
margin: 0 0 12px;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
font-size: 13px;
line-height: 1.428571429;
color: #333;
font-weight: normal;
font-style: normal;
background-color: #f5f5f5;
border: 1px solid #ccc;
overflow-x: auto;
border-radius: 4px;
}
/* responsive */
@media (max-width: 959px) {
.page {
width: auto;
margin: 0;
}
}
</style>
</head>
<body>
<div class="page">
<div class="demo">
<h1 style="font-family: 'TTHovesPro-DmBd'; font-weight: 600; font-style: normal;">☝︎TT Hoves Pro DemiBold</h1>
<pre title="Usage">.your-style {
font-family: 'TTHovesPro-DmBd';
font-weight: 600;
font-style: normal;
}</pre>
<pre title="Preload (optional)">
&lt;link rel=&quot;preload&quot; href=&quot;TTHovesPro-DmBd.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'TTHovesPro-DmBd'; font-weight: 600; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</p>
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
</div>
</div>
<div class="demo">
<h1 style="font-family: 'TTHovesPro-Md'; font-weight: 500; font-style: normal;">☝︎TT Hoves Pro Medium</h1>
<pre title="Usage">.your-style {
font-family: 'TTHovesPro-Md';
font-weight: 500;
font-style: normal;
}</pre>
<pre title="Preload (optional)">
&lt;link rel=&quot;preload&quot; href=&quot;TTHovesPro-Md.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'TTHovesPro-Md'; font-weight: 500; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</p>
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
</div>
</div>
<div class="demo">
<h1 style="font-family: 'TTHovesPro-Rg'; font-weight: normal; font-style: normal;">☝︎TT Hoves Pro Regular</h1>
<pre title="Usage">.your-style {
font-family: 'TTHovesPro-Rg';
font-weight: normal;
font-style: normal;
}</pre>
<pre title="Preload (optional)">
&lt;link rel=&quot;preload&quot; href=&quot;TTHovesPro-Rg.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'TTHovesPro-Rg'; font-weight: normal; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</p>
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
</div>
</div>
</div>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
@font-face {
font-family: 'TTHovesPro';
src: url('TTHovesPro-DmBd.eot');
src:
url('TTHovesPro-DmBd.eot?#iefix') format('embedded-opentype'),
url('TTHovesPro-DmBd.woff2') format('woff2'),
url('TTHovesPro-DmBd.woff') format('woff'),
url('TTHovesPro-DmBd.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'TTHovesPro';
src: url('TTHovesPro-Md.eot');
src:
url('TTHovesPro-Md.eot?#iefix') format('embedded-opentype'),
url('TTHovesPro-Md.woff2') format('woff2'),
url('TTHovesPro-Md.woff') format('woff'),
url('TTHovesPro-Md.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'TTHovesPro';
src: url('TTHovesPro-Rg.eot');
src:
url('TTHovesPro-Rg.eot?#iefix') format('embedded-opentype'),
url('TTHovesPro-Rg.woff2') format('woff2'),
url('TTHovesPro-Rg.woff') format('woff'),
url('TTHovesPro-Rg.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
+3
View File
@@ -0,0 +1,3 @@
export function Footer() {
return <footer></footer>;
}
+38
View File
@@ -0,0 +1,38 @@
import { Link } from 'react-router-dom';
import { Logo } from '../components/icons/Logo';
import { Button } from '../components/ui/Button';
import { ClassNameWrapper } from '../hocs/ClassNameWrapper';
export function Header() {
return (
<header className="lg:px-6 flex items-center h-16 border-b border-[#3D425C]">
<Link to={'/'}>
<ClassNameWrapper element={<Logo />} className="h-10" />
</Link>
<nav className="mx-auto self-stretch flex">
{[
{ path: '/#products', text: 'Продукты' },
{ path: '/#devices', text: 'Оборудование' },
{ path: '/#projects', text: 'Проекты' },
{ path: '/#contacts', text: 'Контакты' },
].map(link => (
<HashLink key={link.path} {...link} />
))}
</nav>
<Button className="-mr-6 rounded-none h-full px-10 btn-text">
Отправить заявку
</Button>
</header>
);
}
function HashLink({ path, text }: { path: string; text: string }) {
return (
<Link
className="border-l last:border-r border-[#3D425C] px-10 self-stretch text-[#9299BD] btn-text content-center hover:bg-[#3D425C]"
to={path}
>
{text}
</Link>
);
}
+15
View File
@@ -0,0 +1,15 @@
import { Outlet } from 'react-router-dom';
import { Footer } from './Footer';
import { Header } from './Header';
export function Layout() {
return (
<>
<Header />
<main className="lg:px-6">
<Outlet />
</main>
<Footer />
</>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

+28
View File
@@ -0,0 +1,28 @@
import { Showreel } from './Showreel';
export function Motivation() {
return (
<div className="pt-20">
<div className="space-y-12 mb-6">
<h1 className="h1 font-medium">
Создаем интерактивные
<span className="text-gradient"> выставочные решения</span>
</h1>
<div className="flex gap-x-5">
{[
{ solution: 'VR и AR', count: 12 },
{ solution: 'Интерактивные приложения', count: 21 },
{ solution: '3D макеты', count: 14 },
].map(({ count, solution }, index) => (
<p key={solution} className="h4 font-medium flex gap-x-[7px]">
{solution}
<sup className="text-xs">{count}</sup>
{index !== 2 && <span>/</span>}
</p>
))}
</div>
</div>
<Showreel />
</div>
);
}
+77
View File
@@ -0,0 +1,77 @@
import { motion } from 'framer-motion';
import { Title } from './ui/Title';
import { promotionFeatures } from '../consts/promotionFeatures';
import { useRef } from 'react';
import { useHover } from 'usehooks-ts';
export function Promotion() {
return (
<div className="pt-[100px] space-y-20">
<Title className="max-w-[calc(1310/1600*100vw)]">
Повышаем количество посетителей на стенде,
<span className="text-gradient">
{' '}
помогаем продвигать ваш продукт и увеличиваем продажи на выставках
</span>
</Title>
<div>
{promotionFeatures.map((feature, index) => (
<Feature key={feature.title} number={index + 1} {...feature} />
))}
</div>
</div>
);
}
function Feature({
description,
images,
number,
title,
}: {
number: number;
title: string;
description: string;
images: string[];
}) {
const ref = useRef<HTMLDivElement>(null);
const hovered = useHover(ref);
return (
<motion.div
ref={ref}
className="border-t border-x border-[#3D425C] last:border-b flex justify-between gap-x-16 items-stretch p-7 transition-all overflow-hidden"
initial={{ height: 196 }}
animate={hovered ? { height: 480 } : { height: 196 }}
>
<div className="max-w-[calc(540/1600*100vw)]">
<div className="flex flex-col justify-between h-full">
<p className="l-text text-[#52587A] font-medium">[0{number}]</p>
<p className="h3 font-medium">{title}</p>
</div>
<motion.p
className="h4 opacity-60 font-medium mt-6"
animate={hovered ? { opacity: 0.6 } : { opacity: 0 }}
>
{description}
</motion.p>
</div>
<div className="flex gap-x-2 justify-end self-start max-w-[calc(736/1600*100vw)]">
{images.map((image, index) => (
<motion.img
key={image}
src={image}
alt=""
className=""
initial={{ maxHeight: (140 / 1600) * window.innerWidth }}
animate={
index === 0 && hovered
? { minHeight: (424 / 1600) * window.innerWidth }
: { maxHeight: (140 / 1600) * window.innerWidth }
}
/>
))}
</div>
</motion.div>
);
}
+11
View File
@@ -0,0 +1,11 @@
import { FullScreenIcon } from './icons/FullScreenIcon';
export function Showreel() {
return (
<div className="aspect-[1552/616] [background:linear-gradient(rgba(0,0,0,0.2),rgba(0,0,0,0.2)),center_url(src/assets/motivation/Showreel.png)] flex justify-center items-center">
<button className="p-[22px] rounded-full border bg-[#14161F33]">
<FullScreenIcon />
</button>
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
export function FullScreenIcon() {
return (
<svg
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.31723 23.3335L11.3256 18.3251L9.67569 16.6752L4.66732 21.6836V16.6752L2.33398 19.0085V25.6668H8.99228L11.3256 23.3335H6.31723Z"
fill="white"
/>
<path
d="M23.334 6.31675V11.3251L25.6673 8.99179V2.3335L19.009 2.3335L16.6757 4.66683H21.6841L16.6757 9.6752L18.3256 11.3251L23.334 6.31675Z"
fill="white"
/>
</svg>
);
}
File diff suppressed because one or more lines are too long
+42
View File
@@ -0,0 +1,42 @@
import { ReactNode } from 'react';
interface ButtonProps {
children: ReactNode;
icon?: JSX.Element;
color?: 'primary' | 'secondary';
width?: 'fit' | 'full';
disabled?: boolean;
className?: string;
onClick?: () => void;
}
export function Button({
children,
color = 'primary',
icon,
width = 'fit',
disabled = false,
className,
onClick,
}: ButtonProps) {
return (
<button
disabled={disabled}
onClick={onClick}
className={`group relative px-6 py-2 rounded-full min-w-fit ${
(color === 'primary'
? 'bg-gradient-to-r from-[#798FFF] to-[#D375FF]'
: '') ||
(color === 'secondary' ? 'outline outline-1 outline-[#3D425C]' : '')
} ${
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={'relative font-medium' + (icon ? '' : ' m-auto')}>
{children}
</span>
<span className="relative">{icon}</span>
</button>
);
}
+8
View File
@@ -0,0 +1,8 @@
import { PropsWithChildren } from 'react';
export function Title({
className = '',
children,
}: PropsWithChildren<{ className?: string }>) {
return <h1 className={'font-medium h2 ' + className}>{children}</h1>;
}
+64
View File
@@ -0,0 +1,64 @@
import { IPromotionFeature } from '../types/IPromotionFeature';
export const promotionFeatures: IPromotionFeature[] = [
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Интерактивные презентации',
images: [
'src/assets/promotion/presentations/1.png',
'src/assets/promotion/presentations/2.png',
'src/assets/promotion/presentations/3.png',
],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: '3D интерактивные презентации техники и оборудования',
images: [
'src/assets/promotion/devices/1.png',
'src/assets/promotion/devices/2.png',
],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Интерактивный каталог продукции',
images: ['src/assets/promotion/catalog/1.png'],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: '3D интерактивные макеты',
images: [
'src/assets/promotion/templates/1.png',
'src/assets/promotion/templates/2.png',
],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Приложения с виртуальной и дополненной реальностью',
images: [
'src/assets/promotion/arvr/1.png',
'src/assets/promotion/arvr/2.png',
'src/assets/promotion/arvr/3.png',
],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Виртуальные выставки и метавселенные',
images: ['src/assets/promotion/exhibitions/1.png'],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Мобильные приложения',
images: [
'src/assets/promotion/mobiles/1.png',
'src/assets/promotion/mobiles/2.png',
'src/assets/promotion/mobiles/3.png',
],
},
];
+20
View File
@@ -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>;
}
+14
View File
@@ -0,0 +1,14 @@
import { useLayoutEffect, useState } from 'react';
export function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useLayoutEffect(() => {
const updateWidth = () => setWidth(window.innerWidth);
window.addEventListener('resize', updateWidth);
updateWidth();
return () => window.removeEventListener('resize', updateWidth);
}, []);
return width;
}
+86
View File
@@ -0,0 +1,86 @@
@import url('/fonts/TTHovesProAll/stylesheet.css');
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: 'TTHovesPro';
color: #fff;
background-color: #14161f;
}
@layer components {
.h1 {
@apply -tracking-[.02em] text-[clamp(36px,36px+(100vw-360px)/1240*76,112px)] leading-[clamp(36px,36px+(100vw-360px)/1240*50.4,86.4px)];
}
.h2 {
@apply -tracking-[.02em] text-[clamp(24px,24px+(100vw-360px)/1240*48,72px)] leading-none;
}
.h3 {
@apply text-[clamp(18px,18px+(100vw-360px)/1240*22,40px)] leading-[clamp(22px,22px+(100vw-360px)/1240*6.8,28.8px)];
}
.h4 {
@apply text-[clamp(14px,14px+(100vw-360px)/1240*6,20px)] leading-[clamp(17.6px,17.6px+(100vw-360px)/1240*6.4,24px)];
}
/* .accent {
@apply -tracking-[.02em] md:text-[clamp(28px,28px+(100vw-768px)/832*4,32px)] text-[clamp(20px,20px+(100vw-360px)/408*8,28px)] md:leading-[clamp(28px,28px+(100vw-768px)/832*7.2,35.2px)] leading-[clamp(20px,20px+(100vw-360px)/408*8,28px)];
} */
.l-text {
@apply text-[clamp(14px,14px+(100vw-360px)/1240*6,20px)] leading-[clamp(21.6px,21.6px+(100vw-360px)/1240*5.4,27px)];
}
.m-text {
@apply text-[clamp(12px,12px+(100vw-360px)/1240*4,16px)] leading-[clamp(19.6px,19.6px+(100vw-360px)/1240*2.4,22.4px)];
}
/* .l-caption {
@apply text-[clamp(14px,14px+(100vw-360px)/1240*2,16px)] leading-none;
}
.m-caption {
@apply text-[clamp(10px,10px+(100vw-360px)/1240*2,12px)] leading-[clamp(12px,12px+(100vw-360px)/1240*2.4,14.4px)];
} */
.btn-text {
@apply -tracking-[.01em] text-[clamp(12px,12px+(100vw-360px)/1240*8,20px)] leading-none;
}
.link-text {
@apply text-[clamp(12px,12px+(100vw-360px)/1240*4,16px)] tracking-[.02em];
}
.descriptor {
@apply text-[clamp(14px,14px+(100vw-360px)/1240*2,16px)] leading-[clamp(15.6px,15.6px+(100vw-360px)/1240*2.6,18.2px)];
}
/* .feedback-field:focus ~ .feedback-placeholder {
top: 0;
}
.feedback-field:focus ~ .feedback-placeholder-2 {
opacity: 0;
}
.feedback-field:valid ~ .feedback-placeholder {
top: 0;
}
.feedback-field:valid ~ .feedback-placeholder-2 {
opacity: 0;
}
.feedback-field::placeholder {
@apply lg:text-base text-sm font-semibold text-[#77787d];
} */
.text-gradient {
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@apply bg-gradient-to-r from-[#798FFF] to-[#D375FF] bg-clip-text;
}
}
+22
View File
@@ -0,0 +1,22 @@
import { createRoot } from 'react-dom/client';
import './index.css';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './Layout';
import { MainPage } from './pages/MainPage';
createRoot(document.getElementById('root')!).render(
<RouterProvider
router={createBrowserRouter([
{
path: '/',
Component: Layout,
children: [
{
index: true,
Component: MainPage,
},
],
},
])}
/>,
);
+11
View File
@@ -0,0 +1,11 @@
import { Motivation } from '../components/Motivation';
import { Promotion } from '../components/Promotion';
export function MainPage() {
return (
<>
<Motivation />
<Promotion />
</>
);
}
+5
View File
@@ -0,0 +1,5 @@
export interface IPromotionFeature {
title: string;
description: string;
images: string[];
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+12
View File
@@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
+2065
View File
File diff suppressed because it is too large Load Diff