upd
This commit is contained in:
@@ -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={{
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)]">
|
||||
Повышаем количество посетителей на стенде,
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user