This commit is contained in:
2024-09-23 16:48:24 +05:00
parent 7f9fda03ec
commit 824be0bbd1
6 changed files with 56 additions and 56 deletions
+8 -5
View File
@@ -3,7 +3,7 @@ import { devices } from '../consts/devices';
import { IDevice } from '../types/IDevice';
import { Title } from './ui/Title';
import { useEffect, useRef, useState } from 'react';
import { useHover } from 'usehooks-ts';
import { useHover, useOnClickOutside } from 'usehooks-ts';
import { ChevronUpIcon } from './icons/ChevronUpIcon';
import { ChevronDownIcon } from './icons/ChevronDownIcon';
@@ -57,6 +57,7 @@ function DesktopDevice({
? (root.current?.clientHeight ?? 0) + descriptionHeight + 24
: 112,
}}
transition={{ delay: 0.3 }}
>
<div className="space-y-6">
<p className="font-medium h3">{title}</p>
@@ -86,7 +87,7 @@ function DesktopDevice({
initial={{ opacity: 0, y: 500 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 100 }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.5, delay: 0.3 }}
/>
)}
</AnimatePresence>
@@ -97,13 +98,13 @@ function DesktopDevice({
function Device({ title, description, img }: IDevice) {
const [expanded, setExpanded] = useState(false);
const [descriptionHeight, setDescriptionHeight] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
const root = useRef<HTMLDivElement>(null);
const descriptionRef = useRef<HTMLParagraphElement>(null);
const imgRef = useRef<HTMLImageElement>(null);
const [descriptionHeight, setDescriptionHeight] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
useEffect(() => {
if (!imgRef.current) return;
imgRef.current!.onload = () =>
@@ -114,6 +115,8 @@ function Device({ title, description, img }: IDevice) {
setDescriptionHeight(descriptionRef.current?.clientHeight ?? 0);
}, [descriptionRef, expanded]);
useOnClickOutside(root, () => setExpanded(false));
return (
<motion.div
animate={{
+1 -1
View File
@@ -3,7 +3,7 @@ import { Title } from './ui/Title';
export function Interactions() {
return (
<div className="items-start space-y-8 lg:flex sm:max-lg:space-y-10 gap-x-4">
<div className="items-start max-sm:space-y-8 lg:flex sm:max-lg:space-y-10 gap-x-4">
<Title className="lg:sticky top-10 max-lg:hidden max-w-[50vw]">
С помощью интерактивных инструментов создаем систему{' '}
<span className="text-gradient">
+9 -6
View File
@@ -9,12 +9,15 @@ export function Footer() {
<Logo />
</Link>
<div className="flex flex-col gap-y-1">
<Link
to="https://graff.tech/privacypolicy"
className="flex gap-4 sm:font-medium m-text"
>
Политика конфиденциальности <span>graff.tech</span>
</Link>
<div className="flex gap-x-4">
<Link
to="https://graff.tech/privacypolicy"
className="flex gap-4 sm:font-medium m-text"
>
Политика конфиденциальности
</Link>
<Link to="https://graff.tech">graff.tech</Link>
</div>
<p className="opacity-40 sm:font-medium m-text">
© 2024 GRAFF interactive. Все права защищены
</p>
+3 -3
View File
@@ -24,7 +24,7 @@ export function Header() {
);
return (
<header className="lg:px-6 px-4 flex max-lg:justify-between items-center lg:h-16 h-12 border-b border-[#3D425C] bg-[#14161F]">
<header className="sticky top-0 lg:px-6 px-4 flex max-lg:justify-between items-center lg:h-16 h-12 border-b border-[#3D425C] bg-[#14161F] z-50">
<Link to={'/'} className="max-sm:hidden">
<ClassNameWrapper element={<Logo />} className="h-8 lg:h-10" />
</Link>
@@ -34,8 +34,8 @@ export function Header() {
<nav className="flex self-stretch mx-auto max-lg:hidden">
{[
{ path: '/#products', text: 'Продукты' },
{ path: '/#devices', text: 'Оборудование' },
{ path: '/#projects', text: 'Проекты' },
{ path: '/#devices', text: 'Оборудование' },
{ path: '/#contacts', text: 'Контакты' },
].map(link => (
<HashLink key={link.path} {...link} />
@@ -75,8 +75,8 @@ export function Header() {
>
<div>
<HashLink text="Продукты" path={'#products'} />
<HashLink text="Оборудование" path={'#devices'} />
<HashLink text="Проекты" path={'#projects'} />
<HashLink text="Оборудование" path={'#devices'} />
<HashLink text="Контакты" path={'#contacts'} />
</div>
<div className="grid grid-cols-[2fr_1fr_1fr] sm:grid-cols-2">
+25 -21
View File
@@ -7,7 +7,6 @@ import { hashes } from '../consts/motivationHashes';
import { ChevronUpIcon } from './icons/ChevronUpIcon';
import { ChevronDownIcon } from './icons/ChevronDownIcon';
import { useWindowWidth } from '../hooks/useWindowWidth';
import { ClassNameWrapper } from '../hocs/ClassNameWrapper';
import { useOnClickOutside } from 'usehooks-ts';
export function Promotion() {
@@ -16,7 +15,7 @@ export function Promotion() {
return (
<div
id="products"
className="space-y-8 lg:space-y-20 sm:space-y-10 lg:-mt-20 sm:-mt-10"
className="space-y-8 lg:space-y-20 sm:space-y-10 lg:-mt-4 sm:-mt-10"
>
<Title className="max-w-[calc(1310/1600*100vw)]">
Повышаем количество посетителей на&nbsp;стенде,
@@ -73,7 +72,7 @@ function DesktopFeature({
<div
id={hashes.get(number)}
onClick={() => setExpanded(prev => !prev)}
className="py-7 border-t border-[#3D425C] cursor-pointer relative last:border-b bg-[url(/images/promotion/Ellipse.png)] bg-[length:0%_0%] hover:bg-[length:100%_100%] transition-all bg-no-repeat bg-top"
className="select-none py-7 border-t border-[#3D425C] cursor-pointer relative last:border-b bg-[url(/images/promotion/Ellipse.png)] bg-[length:0%_0%] hover:bg-[length:100%_100%] transition-all bg-no-repeat bg-top"
ref={ref}
>
<motion.div
@@ -86,14 +85,13 @@ function DesktopFeature({
<div className="max-w-[40vw] flex flex-col xl:gap-y-12 gap-y-6 justify-between h-full">
<p className="l-text text-[#52587A] font-medium">[0{number}]</p>
<div className="space-y-6">
<p className="text-[32px] leading-none font-medium transition-all duration-700">
{title}
</p>
<p className="text-[32px] leading-none font-medium">{title}</p>
{expanded && (
<motion.p
className="font-medium h4 opacity-60"
initial={{ opacity: 0 }}
animate={expanded ? { opacity: 0.6 } : { opacity: 0 }}
transition={{ delay: 1 }}
transition={{ delay: 0.25 }}
>
{description}
</motion.p>
@@ -116,7 +114,7 @@ function DesktopFeature({
src={image}
alt={title}
transition={{ duration: 0.5 }}
className="h-full "
className="h-full pointer-events-none"
animate={{
maxHeight:
expanded && index === 0 ? 0.265 * width : 0.0875 * width,
@@ -145,44 +143,50 @@ function Feature({
number: number;
}) {
const [expanded, setExpanded] = useState(false);
const [descriptionHeight, setDescriptionHeight] = useState(0);
const { hash } = useLocation();
const ref = useRef<HTMLDivElement>(null);
const descriptionRef = useRef<HTMLParagraphElement>(null);
useEffect(() => {
setExpanded(hash.slice(1) === hashes.get(number));
}, [hash, number]);
useEffect(() => {
setDescriptionHeight(descriptionRef.current?.clientHeight ?? 0);
}, [expanded, descriptionRef]);
useEffect(() => {
if (!ref.current) return;
ref.current.style.height = `${ref.current.clientHeight} ${descriptionHeight}px`;
}, [descriptionHeight, expanded]);
useOnClickOutside(ref, () => setExpanded(false));
return (
<div
ref={ref}
id={hashes.get(number)}
className="sm:py-4 py-2 sm:space-y-8 space-y-4 border-t border-[#3D425C] last:border-b flex flex-col justify-between relative"
className="select-none sm:py-4 py-2 sm:space-y-8 space-y-4 border-t border-[#3D425C] last:border-b flex flex-col justify-between relative transition-all"
onClick={() => setExpanded(prev => !prev)}
>
<div className="flex items-center justify-between max-sm:items-start gap-x-5">
<div className="flex items-center justify-between gap-x-5">
<p className="font-medium h3">{title}</p>
<div className="border p-2 border-[#3D425C] rounded-full">
<ClassNameWrapper
className="flex-1"
element={expanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
/>
{expanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
</div>
</div>
<AnimatePresence>
{expanded && (
<motion.p
ref={descriptionRef}
className="font-medium l-text"
transition={{ delay: 1 }}
initial={{ opacity: 0 }}
animate={
expanded
? { opacity: 0.6, transition: { delay: 0.5 } }
: { opacity: 0 }
}
transition={{ delay: 0.25 }}
initial={{ opacity: 0, y: 10 }}
animate={expanded ? { opacity: 0.6, y: 0 } : { opacity: 0, y: 10 }}
exit={{ opacity: 0, y: 10 }}
>
{description}
</motion.p>
+10 -20
View File
@@ -3,8 +3,8 @@ import { IPromotionFeature } from '../types/IPromotionFeature';
export const promotionFeatures: IPromotionFeature[] = [
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Интерактивные презентации',
'Инструмент для демонстрации вашего продукта или проекта в 3D формате с возможностью донести в максимально удобном виде любые особенности и ключевые преимущества.',
title: '3D интерактивные презентации',
images: [
'/images/promotion/presentations/1.png',
'/images/promotion/presentations/2.png',
@@ -13,8 +13,8 @@ export const promotionFeatures: IPromotionFeature[] = [
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: '3D интерактивные презентации техники и оборудования',
'Дает возможность посетителям самостоятельно искать, фильтровать, сравнивать и рассматривать 3D модели продуктов.',
title: 'Интерактивный каталог продукции',
images: [
'/images/promotion/devices/1.png',
'/images/promotion/devices/2.png',
@@ -22,29 +22,19 @@ export const promotionFeatures: IPromotionFeature[] = [
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Интерактивный каталог продукции',
'Комплекс сочетающий в себя физический макет и трехмерный интерактивный контентом. Сочетание физического макета и виртуального контента создает эффект присутствия и погружения. Посетители могут лучше прочувствовать замысел и идею проекта.',
title: '3D интерактивные макеты',
images: ['/images/promotion/catalog/1.png'],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: '3D интерактивные макеты',
'Дают возможность удаленно демонстрировать экспонаты, проводить презентации, организовывать виртуальные туры. Это повышает доступность и географию охвата мероприятия.',
title: 'Приложения с виртуальной и дополненной реальностью',
images: [
'/images/promotion/templates/1.png',
'/images/promotion/templates/2.png',
],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
title: 'Приложения с виртуальной и дополненной реальностью',
images: [
'/images/promotion/arvr/1.png',
'/images/promotion/arvr/2.png',
'/images/promotion/arvr/3.png',
],
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
@@ -53,7 +43,7 @@ export const promotionFeatures: IPromotionFeature[] = [
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
'Технология позволяет создавать интерактивные экспонаты с распознаванием жестов и движений посетителей для управления интерактивными дисплеями и 3D моделями.',
title: 'Компьютерное зрение',
images: [
'/images/promotion/computer_vision/1.png',
@@ -62,7 +52,7 @@ export const promotionFeatures: IPromotionFeature[] = [
},
{
description:
'Ключевые преимущества жилого комплекса показаны на интерактивной модели. Для этого создается маршрут, демонстрирующий детали комплекса, важные для покупателя',
'Используюся для инструмент для управления контентом сторонних приложений и взаимодействия с дополненной реальностью',
title: 'Мобильные приложения',
images: [
'/images/promotion/mobiles/1.png',