upd
This commit is contained in:
+7
-7
@@ -111,15 +111,15 @@ html {
|
||||
}
|
||||
|
||||
@utility line1 {
|
||||
@apply lg:text-[clamp(96px,4.444vw,128px)] md:max-lg:text-[clamp(92px,92px+(100vw-768px)/672*8,100px)] sm:max-md:text-[clamp(40px,40px+(100vw-360px)/408*16,56px)] text-[40px] leading-[85%];
|
||||
@apply 2xl:text-[128px] lg:max-2xl:text-[clamp(96px,4.444vw,128px)] md:max-lg:text-[clamp(92px,92px+(100vw-768px)/672*8,100px)] sm:max-md:text-[clamp(40px,40px+(100vw-360px)/408*16,56px)] text-[40px] leading-[85%];
|
||||
}
|
||||
|
||||
@utility line2 {
|
||||
@apply lg:text-[clamp(64px,4.444vw,88px)] md:max-lg:text-[clamp(40px,40px+(100vw-768px)/672*24,64px)] sm:max-md:text-[clamp(32px,32px+(100vw-360px)/408*8,40px)] text-[32px] leading-[95%];
|
||||
@apply 2xl:text-[88px] lg:max-2xl:text-[clamp(64px,4.444vw,88px)] md:max-lg:text-[clamp(40px,40px+(100vw-768px)/672*24,64px)] sm:max-md:text-[clamp(32px,32px+(100vw-360px)/408*8,40px)] text-[32px] leading-[95%];
|
||||
}
|
||||
|
||||
@utility heading1 {
|
||||
@apply lg:text-[clamp(24px,24px+(100vw-1440px)/480*4,32px)] md:max-lg:text-2xl text-xl leading-[1.167];
|
||||
@apply 2xl:text-[2.083vw] lg:max-2xl:text-[clamp(24px,24px+(100vw-1440px)/480*4,32px)] md:max-lg:text-2xl text-xl leading-[1.167];
|
||||
}
|
||||
|
||||
@utility heading2 {
|
||||
@@ -127,19 +127,19 @@ html {
|
||||
}
|
||||
|
||||
@utility accent {
|
||||
@apply 2xl:text-[2.604vw] lg:max-2xl:text-[clamp(32px,32px+(100vw-1440px)/480*8,40px)] text-[clamp(24px,32px+(100vw-360px)/1080*8,32px)] lg:leading-[1.1] leading-none;
|
||||
@apply 2xl:text-[2.604vw] lg:max-2xl:text-[clamp(32px,32px+(100vw-1440px)/480*8,40px)] text-[clamp(16px,16px+(100vw-360px)/1080*8,32px)] lg:leading-[1.1] leading-none;
|
||||
}
|
||||
|
||||
@utility text1 {
|
||||
@apply 2xl:text-[1.172vw] lg:max-2xl:text-[clamp(14px,14px+(100vw-1440px)/480*4,18px)] text-sm leading-[1.35];
|
||||
@apply 2xl:text-2xl lg:max-2xl:text-[clamp(14px,14px+(100vw-1440px)/480*4,18px)] text-sm leading-[1.35];
|
||||
}
|
||||
|
||||
@utility text2 {
|
||||
@apply 2xl:text-[0.911vw] lg:max-2xl:text-[clamp(12px,12px+(100vw-1440px)/480*2,14px)] text-xs leading-[1.35];
|
||||
@apply 2xl:text-xl lg:max-2xl:text-[clamp(12px,12px+(100vw-1440px)/480*2,14px)] text-xs leading-[1.35];
|
||||
}
|
||||
|
||||
@utility btnl {
|
||||
@apply lg:text-[1.25vw] md:max-lg:text-[18px] sm:max-md:text-[clamp(16px,16px+(100vw-360px)/1560*2,18px)] text-base leading-none;
|
||||
@apply md:text-2xl sm:max-md:text-[clamp(16px,16px+(100vw-360px)/1560*2,18px)] text-base leading-none;
|
||||
}
|
||||
|
||||
@utility btnm {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SyntheticEvent } from 'react';
|
||||
import EditIcon from '../../public/icons/edit.svg';
|
||||
import TrashIcon from '../../public/icons/trash.svg';
|
||||
import { DeleteItemModal } from './DeleteItemModal';
|
||||
import { ArticleFormModal } from './modals/ArticleFormModal';
|
||||
import { ArticleContentFormModal } from './modals/ArticleContentFormModal';
|
||||
import { CompanyFormModal } from './modals/CompanyFormModal';
|
||||
import { ProjectFormModal } from './modals/ProjectFormModal';
|
||||
|
||||
@@ -38,12 +38,11 @@ export function ItemActions({
|
||||
<CompanyFormModal action="edit" defaultValues={company} />,
|
||||
'editCompanyForm'
|
||||
);
|
||||
} else {
|
||||
} else
|
||||
setModal(
|
||||
<ArticleFormModal action="edit" defaultValues={item} />,
|
||||
'editArticleForm'
|
||||
<ArticleContentFormModal {...item} />,
|
||||
'articleContentFormModal'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(e: SyntheticEvent) {
|
||||
|
||||
@@ -83,7 +83,7 @@ export function FeedbackForm() {
|
||||
currentPhoneCodeAndCountry={[phoneCode, country]}
|
||||
onClick={setPhoneCodeAndCountry}
|
||||
/>
|
||||
<div className="border-l border-[#3D425C]" />
|
||||
<div className="border-l border-[#37393B]" />
|
||||
<ReactInputMask
|
||||
type="tel"
|
||||
id={'tel'}
|
||||
@@ -100,7 +100,7 @@ export function FeedbackForm() {
|
||||
type="text"
|
||||
placeholder="E-mail*"
|
||||
{...register('email')}
|
||||
className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<div className="sm:flex items-stretch gap-3">
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import Link from 'next/link';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import ArrowMoreIcon from '../../../public/icons/arrow_more.svg';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
@@ -11,13 +12,9 @@ export function Footer() {
|
||||
className="p-6 flex flex-col justify-between max-sm:gap-y-10 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl flex-1"
|
||||
>
|
||||
<p className="text-[#7A7A7A] text1 font-medium">Позвонить</p>
|
||||
<div className="lg:line2 sm:heading1 line2 flex items-center font-medium">
|
||||
<div className="lg:line2 sm:max-lg:heading1 line2 flex items-center font-medium">
|
||||
8 800 770 00 67
|
||||
<Icon
|
||||
name="arrow_more"
|
||||
color="white"
|
||||
svgProp={{ className: 'lg:w-20 lg:h-20 sm:w-8 sm:h-8 w-9 h-9' }}
|
||||
/>
|
||||
<ArrowMoreIcon className="text-white lg:w-20 lg:h-20 md:max-lg:w-8 md:max-lg:h-8 w-9 h-9" />
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
@@ -25,17 +22,13 @@ export function Footer() {
|
||||
className="p-6 flex flex-col justify-between max-sm:gap-y-10 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl flex-1"
|
||||
>
|
||||
<p className="text-[#7A7A7A] text1 font-medium">Написать</p>
|
||||
<div className="lg:line2 sm:heading1 line2 flex items-center font-medium">
|
||||
<div className="lg:line2 sm:max-lg:heading1 line2 flex items-center font-medium">
|
||||
info@graff.tech
|
||||
<Icon
|
||||
name="arrow_more"
|
||||
color="white"
|
||||
svgProp={{ className: 'lg:w-20 lg:h-20 sm:w-8 sm:h-8 w-9 h-9' }}
|
||||
/>
|
||||
<ArrowMoreIcon className="text-white lg:w-20 lg:h-20 md:max-lg:w-8 md:max-lg:h-8 w-9 h-9" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="gap-y-2 justify-stretch sm:gap-x-2 gap-x-1 sm:flex-col flex">
|
||||
<ContactLink href="https://t.me/GRAFFinteractive">
|
||||
<ContactLink href="https://t.me/graffestate">
|
||||
<Icon
|
||||
name="tg"
|
||||
svgProp={{
|
||||
|
||||
@@ -75,28 +75,28 @@ export function Header() {
|
||||
animate={{
|
||||
width:
|
||||
burgerOpened && (isXs || isSm)
|
||||
? 340
|
||||
? 320
|
||||
: productsOpened && !isXs && !isSm
|
||||
? isLg
|
||||
? 'calc(992 / 1440 * 100vw)'
|
||||
? '68.889vw'
|
||||
: 'calc(100vw - 32px)'
|
||||
: 'auto',
|
||||
}}
|
||||
className="fixed lg:top-5 top-4 p-1 rounded-[20px] bg-[#37393B99] backdrop-blur-2xl flex gap-1 z-[12]"
|
||||
className="fixed lg:top-5 top-4 p-1 rounded-[20px] bg-[#37393B99] backdrop-blur-2xl flex gap-1 z-12"
|
||||
>
|
||||
{((isLg && scroll < -logoRef.current?.clientHeight!) || !isLg) && (
|
||||
<Link
|
||||
href={'/'}
|
||||
className="aspect-square p-4 alg:hidden hover:bg-[#37393B99] rounded-xl content-center"
|
||||
className="aspect-square p-3 hover:bg-[#37393B99] rounded-2xl content-center"
|
||||
>
|
||||
<ClassNameWrapper className={'w-4 h-4'}>
|
||||
<ClassNameWrapper className={'w-6 h-6'}>
|
||||
<LogoIcon />
|
||||
</ClassNameWrapper>
|
||||
</Link>
|
||||
)}
|
||||
<div ref={productsBtnRef} className="max-md:hidden">
|
||||
<button
|
||||
className="px-6 py-4 font-medium btnm hover:bg-[#37393B99] rounded-2xl active:bg-white active:text-black"
|
||||
className="px-6 py-4 font-medium btnm hover:bg-[#37393B99] rounded-2xl active:bg-white active:text-black cursor-pointer outline-none"
|
||||
onClick={() => {
|
||||
setProductsOpened((prev) => !prev);
|
||||
}}
|
||||
@@ -157,7 +157,7 @@ export function Header() {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 100 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute w-full p-4 pt-2 top-16 rounded-2xl bg-[#232425] md:hidden space-y-6"
|
||||
className="absolute p-4 pt-2 top-16 rounded-2xl bg-[#232425] md:hidden space-y-6 xs:max-h-[calc(100dvh-100px)] h-fit"
|
||||
>
|
||||
<div className="px-2 -mx-4 space-y-1">
|
||||
<HeaderLink
|
||||
@@ -165,7 +165,11 @@ export function Header() {
|
||||
text={'О нас'}
|
||||
className="accent"
|
||||
/>
|
||||
<HeaderLink href={'/'} text={'Блог'} className="accent" />
|
||||
<HeaderLink
|
||||
href={'/blog'}
|
||||
text={'Блог'}
|
||||
className="accent"
|
||||
/>
|
||||
<HeaderLink
|
||||
href={'/projects'}
|
||||
text={'Проекты'}
|
||||
@@ -187,7 +191,7 @@ export function Header() {
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="fixed w-screen h-screen bg-[#0F101199] backdrop-blur-lg -top-4 -left-[calc((100vw-100%)/2)] -z-[1]" />
|
||||
<div className="absolute w-screen h-screen bg-[#0F101199] backdrop-blur-lg -top-4 -left-[calc((100vw-100%)/2)] -z-[1]" />
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ProductItem } from '@/ui/ProductItem';
|
||||
|
||||
export function Products() {
|
||||
return (
|
||||
<div className="grid md:grid-cols-2 grid-cols-3 md:grid-rows-6 grid-rows-2 gap-1 lg:gap-2 rounded-2xl max-h-[calc(100dvh-100px)]">
|
||||
<div className="grid md:grid-cols-2 grid-cols-3 md:grid-rows-6 grid-rows-2 gap-1 lg:gap-2 rounded-2xl max-h-[calc(100dvh-100px)] lg:w-[68.889vw] lg:h-[38.889vw] md:max-lg:w-[95.703vw] md:h-[72.917vw] h-full w-full">
|
||||
{products.map((product, index) => (
|
||||
<ProductItem
|
||||
href={'/' + product.title.toLowerCase()}
|
||||
@@ -11,14 +11,10 @@ export function Products() {
|
||||
{...product}
|
||||
className={
|
||||
index < 2
|
||||
? `md:aspect-[361.5/263] md:col-start-1 md:row-span-3`
|
||||
? 'max-md:aspect-[100/114] md:col-start-1 md:row-span-3'
|
||||
: index === 4
|
||||
? `col-span-2 md:col-span-1 md:col-start-2 md:row-start-${
|
||||
index * 2 - 3
|
||||
} md:row-end-${index * 2 - 1}`
|
||||
: `max-md:aspect-[100/114] md:col-span-1 md:col-start-2 md:row-start-${
|
||||
index * 2 - 3
|
||||
} md:row-end-${index * 2 - 1}`
|
||||
? 'col-span-2 md:col-span-1 md:col-start-2 md:row-start-5 md:row-end-7'
|
||||
: 'max-md:aspect-[100/114] md:col-span-1 md:col-start-2 md:nth-3:row-start-1 md:nth-3:row-end-3 md:nth-4:row-start-3 md:nth-4:row-end-5'
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -49,9 +49,9 @@ export function SelectPhoneCode({
|
||||
/>
|
||||
<p className="btnl font-medium">{currentPhoneCode}</p>
|
||||
{open ? (
|
||||
<ChevronUpIcon className="max-sm:w-4 sm:max-lg:w-5 flex-1 text-white" />
|
||||
<ChevronUpIcon className="max-sm:w-4 sm:max-lg:w-5 text-white" />
|
||||
) : (
|
||||
<ChevronDownIcon className="max-sm:w-4 sm:max-lg:w-5 flex-1 text-white" />
|
||||
<ChevronDownIcon className="max-sm:w-4 sm:max-lg:w-5 text-white" />
|
||||
)}
|
||||
</button>
|
||||
{open && (
|
||||
|
||||
@@ -1,33 +1,51 @@
|
||||
import { BundledEditor } from '@/lib/BundledEditor';
|
||||
import { useRef } from 'react';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
import { Dispatch, SetStateAction, useRef } from 'react';
|
||||
import { Control, FieldArrayWithId, useController } from 'react-hook-form';
|
||||
import { Editor } from 'tinymce';
|
||||
import { IArticleInput } from '../modals/ArticleFormModal';
|
||||
|
||||
export function ArticleContentEditor({
|
||||
control,
|
||||
index,
|
||||
setEditing,
|
||||
item,
|
||||
}: {
|
||||
index: number;
|
||||
control: Control<IArticleInput, any>;
|
||||
setEditing: Dispatch<SetStateAction<boolean>>;
|
||||
item: FieldArrayWithId<IArticleInput, 'blocks', 'id'> & { content: string };
|
||||
}) {
|
||||
const ref = useRef<Editor | null>(null);
|
||||
|
||||
const {
|
||||
field: { onChange, value },
|
||||
} = useController({
|
||||
control,
|
||||
name: `blocks.${index}.content`,
|
||||
defaultValue: item.content,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`blocks.${index}.content`}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<BundledEditor
|
||||
onEditorChange={onChange}
|
||||
value={value}
|
||||
onInit={(_, editor) => {
|
||||
ref.current = editor;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="w-full space-y-4">
|
||||
<BundledEditor
|
||||
id={item.id}
|
||||
onEditorChange={onChange}
|
||||
value={value}
|
||||
onInit={(_, editor) => {
|
||||
ref.current = editor;
|
||||
}}
|
||||
init={{
|
||||
content_style:
|
||||
'body {color: #fff; background: #14161f; font-size:16px;}',
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditing(false);
|
||||
}}
|
||||
>
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Reorder } from 'framer-motion';
|
||||
import parse from 'html-react-parser';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Control,
|
||||
FieldArrayWithId,
|
||||
UseFieldArrayInsert,
|
||||
UseFieldArrayRemove,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import { BlockActions } from '../BlockActions';
|
||||
import { IArticleInput } from '../modals/ArticleFormModal';
|
||||
@@ -13,7 +14,7 @@ import { ArticleContentEditor } from './ArticleContentEditor';
|
||||
|
||||
export interface IArticleContentInputProps {
|
||||
index: number;
|
||||
item: FieldArrayWithId<IArticleInput, 'blocks', 'id'>;
|
||||
item: FieldArrayWithId<IArticleInput, 'blocks', 'id'> & { content: string };
|
||||
control: Control<IArticleInput, any>;
|
||||
insert: UseFieldArrayInsert<IArticleInput, 'blocks'>;
|
||||
remove: UseFieldArrayRemove;
|
||||
@@ -30,6 +31,19 @@ export function ArticleContentInput({
|
||||
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
const { watch } = useFormContext<IArticleInput>();
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = watch(({ blocks }) => {
|
||||
if (!blocks || !blocks.length || blocks[index]?.type !== 'Content')
|
||||
return;
|
||||
setContent(blocks[index].content ?? '');
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [index, watch]);
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
as="div"
|
||||
@@ -38,10 +52,15 @@ export function ArticleContentInput({
|
||||
onDoubleClick={() => ref.current && ref.current.click()}
|
||||
>
|
||||
{editing ? (
|
||||
<ArticleContentEditor control={control} index={index} />
|
||||
<ArticleContentEditor
|
||||
control={control}
|
||||
index={index}
|
||||
item={item}
|
||||
setEditing={setEditing}
|
||||
/>
|
||||
) : (
|
||||
<div className="border-[#3D425C] border p-4 rounded-3xl no-tailwindcss-base">
|
||||
{item.type === 'Content' && parse(item.content)}
|
||||
{item.type === 'Content' && parse(content)}
|
||||
</div>
|
||||
)}
|
||||
<BlockActions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useArticleMutation } from '@/hooks/useArticleMutation';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { Button } from '@/ui/Button';
|
||||
@@ -37,16 +38,27 @@ export function ArticleContentFormModal({
|
||||
},
|
||||
});
|
||||
|
||||
const { control } = form;
|
||||
const { control, getValues } = form;
|
||||
|
||||
const { append, fields, swap, insert, remove } = useFieldArray({
|
||||
control,
|
||||
name: 'blocks',
|
||||
});
|
||||
|
||||
const { mutateAsync: save } = useArticleMutation({ action: 'edit', id });
|
||||
|
||||
async function handleSave(drafted: boolean) {
|
||||
await save({
|
||||
...getValues(),
|
||||
blocks: JSON.stringify(getValues('blocks')),
|
||||
drafted,
|
||||
});
|
||||
setModal(null, '');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ArticleFormActions disabled={false} handleSave={() => {}} />
|
||||
<ArticleFormActions disabled={false} handleSave={handleSave} />
|
||||
<div className="relative space-y-4 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] h-[calc(100vh-40px)] z-1 overflow-y-auto">
|
||||
<div className="absolute top-3 right-4">
|
||||
<Button
|
||||
@@ -96,48 +108,50 @@ export function ArticleContentFormModal({
|
||||
</div>
|
||||
</div>
|
||||
<FormProvider {...form}>
|
||||
<Reorder.Group
|
||||
axis="y"
|
||||
values={fields}
|
||||
onReorder={reorderFields(fields, swap)}
|
||||
className="pt-10 px-[75px] space-y-5"
|
||||
>
|
||||
{fields.map((item, index) =>
|
||||
item.type === 'Content' ? (
|
||||
<ArticleContentInput
|
||||
key={item.id}
|
||||
control={control}
|
||||
index={index}
|
||||
item={item}
|
||||
insert={insert}
|
||||
remove={remove}
|
||||
/>
|
||||
) : item.type === 'Quote' ? (
|
||||
<ArticleQuoteInput
|
||||
key={item.id}
|
||||
index={index}
|
||||
item={item}
|
||||
control={control}
|
||||
remove={remove}
|
||||
/>
|
||||
) : item.type === 'Slider' ? (
|
||||
<ArticleSliderInput
|
||||
key={item.id}
|
||||
control={control}
|
||||
index={index}
|
||||
item={item}
|
||||
remove={remove}
|
||||
/>
|
||||
) : (
|
||||
<ArticleVideoUploader
|
||||
key={item.id}
|
||||
item={item}
|
||||
index={index}
|
||||
remove={remove}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Reorder.Group>
|
||||
<form>
|
||||
<Reorder.Group
|
||||
axis="y"
|
||||
values={fields}
|
||||
onReorder={reorderFields(fields, swap)}
|
||||
className="pt-10 px-[75px] space-y-5"
|
||||
>
|
||||
{fields.map((item, index) =>
|
||||
item.type === 'Content' ? (
|
||||
<ArticleContentInput
|
||||
key={item.id}
|
||||
control={control}
|
||||
index={index}
|
||||
item={item}
|
||||
insert={insert}
|
||||
remove={remove}
|
||||
/>
|
||||
) : item.type === 'Quote' ? (
|
||||
<ArticleQuoteInput
|
||||
key={item.id}
|
||||
index={index}
|
||||
item={item}
|
||||
control={control}
|
||||
remove={remove}
|
||||
/>
|
||||
) : item.type === 'Slider' ? (
|
||||
<ArticleSliderInput
|
||||
key={item.id}
|
||||
control={control}
|
||||
index={index}
|
||||
item={item}
|
||||
remove={remove}
|
||||
/>
|
||||
) : (
|
||||
<ArticleVideoUploader
|
||||
key={item.id}
|
||||
item={item}
|
||||
index={index}
|
||||
remove={remove}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Reorder.Group>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
<div className="fixed bottom-5 right-20 z-10 space-y-2">
|
||||
|
||||
@@ -72,7 +72,7 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
handleSave={handleSave}
|
||||
disabled={!title || !tags || !cardImage || !posterImage}
|
||||
/>
|
||||
<div className="relative pb-10 space-y-4 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] pl-[75px] pr-[55px] overflow-y-auto z-[2] max-h-[calc(100vh-40px)]">
|
||||
<div className="relative pb-10 space-y-4 bg-[#232425] rounded-[28px] top-5 w-[66.25vw] pl-[75px] pr-[55px] overflow-y-auto z-[2] max-h-[calc(100vh-40px)]">
|
||||
<FormModalHeader
|
||||
submitHandler={handleSubmit(onSubmit)}
|
||||
disabled={!title || !tags || !cardImage || !posterImage}
|
||||
@@ -93,7 +93,7 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
/>
|
||||
<div className="gap-y-4 flex flex-col">
|
||||
<label htmlFor="devices" className="w-fit">
|
||||
Устройства
|
||||
Категории
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{['Недвижимость', 'Награды', 'Выставки'].map((tag) => (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { stories } from '@/consts/stories';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { createRef, RefObject, useEffect, useState } from 'react';
|
||||
import { createRef, RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { useSwipeable } from 'react-swipeable';
|
||||
import CloseIcon from '../../../public/icons/close.svg';
|
||||
|
||||
@@ -14,6 +14,8 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
|
||||
const { isLg, isMd } = useMediaQueries();
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setVideoRefs(stories.map(createRef<HTMLVideoElement>));
|
||||
}, []);
|
||||
@@ -41,7 +43,7 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
if (currentIndex === videoRefs.length - 1) setModal(null, '');
|
||||
else setCurrentIndex((prev) => prev + 1);
|
||||
}
|
||||
}, [currentIndex, currentProgress, setModal, videoRefs]);
|
||||
}, [currentProgress, setModal, videoRefs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -50,15 +52,37 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
!videoRefs[currentIndex].current
|
||||
)
|
||||
return;
|
||||
|
||||
videoRefs.forEach(({ current: video }, index) => {
|
||||
if (index === currentIndex) video?.play();
|
||||
else {
|
||||
if (index === currentIndex) {
|
||||
video!.playbackRate = 10;
|
||||
video?.play();
|
||||
} else {
|
||||
video!.currentTime = 0;
|
||||
video?.pause();
|
||||
}
|
||||
});
|
||||
}, [currentIndex, videoRefs]);
|
||||
|
||||
useEffect(() => {
|
||||
const slider = ref.current;
|
||||
if (!slider || isLg || isMd) return;
|
||||
|
||||
function handleTouch(e: TouchEvent) {
|
||||
if (e.touches[0].clientX > slider!.clientWidth / 2)
|
||||
setCurrentIndex((prev) => Math.min(videoRefs.length - 1, prev + 1));
|
||||
else setCurrentIndex((prev) => Math.max(0, prev - 1));
|
||||
}
|
||||
|
||||
slider.addEventListener('touchstart', handleTouch);
|
||||
|
||||
return () => slider.removeEventListener('touchstart', handleTouch);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('currentIndex', currentIndex);
|
||||
}, [currentIndex]);
|
||||
|
||||
const handlers = useSwipeable({
|
||||
trackMouse: true,
|
||||
preventScrollOnSwipe: true,
|
||||
@@ -71,34 +95,32 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="rounded-2xl p-4 bg-[#37393B99] absolute top-5 right-5 z-1 cursor-pointer"
|
||||
className="rounded-2xl p-4 bg-[#37393B99] absolute top-5 right-5 z-10 cursor-pointer"
|
||||
onClick={() => setModal(null, '')}
|
||||
>
|
||||
<CloseIcon className="w-4 h-4 text-white" />
|
||||
</button>
|
||||
<div
|
||||
{...handlers}
|
||||
className="overflow-hidden z-1 m-auto lg:max-w-[83.958vw] lg:max-h-[76.433vh] md:max-lg:max-w-[157.422vw] md:max-lg:max-h-[67.669vh] max-w-screen max-h-screen"
|
||||
className="overflow-hidden z-1 md:m-auto lg:max-w-[83.958vw] lg:max-h-[76.433vh] md:max-lg:max-w-[157.422vw] md:max-lg:max-h-[67.669vh] max-w-screen max-h-dvh max-md:top-0"
|
||||
>
|
||||
<div
|
||||
className="flex md:gap-6 items-center h-full relative transition-transform"
|
||||
style={{
|
||||
transform: isLg
|
||||
? `translateX(calc(${1 - currentIndex}*26.25vw))`
|
||||
: isMd
|
||||
? `translateX(calc(${1 / 2 - currentIndex}*49.219vw))`
|
||||
: undefined,
|
||||
left: isLg || isMd ? 24 * (1 - currentIndex) : 0,
|
||||
}}
|
||||
>
|
||||
<div ref={ref} className="flex md:gap-6 items-center h-full relative">
|
||||
{!!videoRefs.length &&
|
||||
stories.map(({ id, video, title, poster }, index) => (
|
||||
<div
|
||||
style={{
|
||||
transform: isLg
|
||||
? `translateX(calc(${1 - currentIndex}*26.25vw))`
|
||||
: isMd
|
||||
? `translateX(calc(${1 / 2 - currentIndex}*49.219vw))`
|
||||
: `translateX(${-currentIndex * 100}%)`,
|
||||
left: isLg || isMd ? 24 * (1 - currentIndex) : 0,
|
||||
}}
|
||||
key={id}
|
||||
className={`select-none relative flex items-end overflow-hidden md:rounded-xl cursor-pointer p-4 ${
|
||||
className={`select-none relative flex items-end overflow-hidden md:rounded-xl cursor-pointer transition-transform p-4 ${
|
||||
index === currentIndex
|
||||
? 'lg:min-w-[28.125vw] lg:h-[76.433vh] md:max-lg:min-w-[52.734vw] md:max-lg:h-[67.669vh] min-w-screen h-screen'
|
||||
: 'lg:min-w-[26.25vw] lg:h-[71.338vh] md:max-lg:min-w-[49.219vw] md:max-lg:h-[63.158vh] min-w-screen h-screen'
|
||||
? 'lg:min-w-[28.125vw] lg:h-[76.433vh] md:max-lg:min-w-[52.734vw] md:max-lg:h-[67.669vh] min-w-screen h-dvh'
|
||||
: 'lg:min-w-[26.25vw] lg:h-[71.338vh] md:max-lg:min-w-[49.219vw] md:max-lg:h-[63.158vh] min-w-screen h-dvh'
|
||||
}`}
|
||||
onClick={() => setCurrentIndex(index)}
|
||||
>
|
||||
@@ -106,22 +128,22 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
ref={videoRefs[index]}
|
||||
src={video}
|
||||
poster={poster}
|
||||
className="absolute left-0 top-0 w-full h-full object-cover object-center md:rounded-xl"
|
||||
playsInline
|
||||
className="absolute left-0 top-0 w-full h-full object-cover object-center"
|
||||
/>
|
||||
<div
|
||||
className={`absolute bottom-0 left-1/2 -translate-x-1/2 w-full h-full transition-colors ${
|
||||
className={`absolute bottom-0 left-1/2 -translate-x-1/2 w-full h-full md:rounded-xl transition-colors ${
|
||||
index === currentIndex
|
||||
? 'lg:bg-[radial-gradient(37.292vw_23.036vh_at_bottom,#6078F2,#C868F5,transparent)] md:max-lg:bg-[radial-gradient(69.922vw_20.395vh_at_bottom,#6078F2,#C868F5,transparent)]'
|
||||
: 'bg-[#0F101199]'
|
||||
? 'lg:bg-[radial-gradient(37.292vw_23.036vh_at_bottom,#6078F2,#C868F5,transparent)] md:max-lg:bg-[radial-gradient(69.922vw_20.395vh_at_bottom,#6078F2,#C868F5,transparent)] bg-[radial-gradient(149.167vw_33.906vh_at_bottom,#6078F2,#C868F5,transparent)]'
|
||||
: 'md:bg-[#0F101199]'
|
||||
}`}
|
||||
/>
|
||||
{index === currentIndex && (
|
||||
<div className="space-y-5 z-1">
|
||||
{currentIndex === index && (
|
||||
<div className="space-y-5 z-1 max-md:hidden">
|
||||
<p className="heading1 font-medium">{title}</p>
|
||||
<div className="bg-white/30 relative w-full h-1">
|
||||
<div className="bg-white/30 w-full h-1 rounded-[34px]">
|
||||
<div
|
||||
className="h-1 bg-white absolute transition-[width]"
|
||||
className="h-1 bg-white transition-[width] rounded-[34px]"
|
||||
style={{ width: currentProgress * 100 + '%' }}
|
||||
/>
|
||||
</div>
|
||||
@@ -129,6 +151,25 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="md:hidden absolute space-y-6 left-2.5 right-2.5 bottom-4 z-1 w-full">
|
||||
<p className="heading1 font-medium">
|
||||
{stories[currentIndex].title}
|
||||
</p>
|
||||
<div className="flex gap-1">
|
||||
{stories.map(({ id }, index) => (
|
||||
<div key={id} className="bg-white/40 flex-1 rounded-[34px] h-1">
|
||||
<div
|
||||
className="bg-white rounded-[34px] h-1 transition-all"
|
||||
style={
|
||||
currentIndex === index
|
||||
? { width: currentProgress * 100 + '%' }
|
||||
: { width: currentIndex > index ? '100%' : '0%' }
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useGetArticleById } from '@/queries/getArticleById';
|
||||
import parse from 'html-react-parser';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function ArticleSyncPage({ articleId }: { articleId: string }) {
|
||||
@@ -35,7 +36,11 @@ export function ArticleSyncPage({ articleId }: { articleId: string }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-10 px-[75px]"></div>
|
||||
<div className="py-10 px-[75px]">
|
||||
{article.blocks.map(
|
||||
(block) => block.type === 'Content' && parse(block.content)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function ArticlesPageActions({ tags }: { tags: string[] }) {
|
||||
modal={<ArticleFormModal action="create" />}
|
||||
>
|
||||
<Button
|
||||
className="px-3 py-2 outline-none btns"
|
||||
className="px-3 py-2 outline-none btns flex items-center text-nowrap"
|
||||
rounded="xl"
|
||||
icon={<AddIcon className="w-4 h-4 text-white" />}
|
||||
>
|
||||
@@ -31,7 +31,7 @@ export function ArticlesPageActions({ tags }: { tags: string[] }) {
|
||||
{auth && (
|
||||
<Button
|
||||
color="secondary"
|
||||
className={`active:bg-white active:text-black px-3 transition-colors py-2 btns ${
|
||||
className={`active:bg-white active:text-black px-3 transition-colors text-nowrap py-2 btns ${
|
||||
show ? 'bg-white text-black' : 'bg-[#37393B99]'
|
||||
}`}
|
||||
rounded="xl"
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import { awards } from '@/consts/awards';
|
||||
import { useScroll } from '@/hooks/useScroll';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { useInView } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
import { useRef } from 'react';
|
||||
|
||||
export function Awards() {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const target = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scroll = useScroll(ref);
|
||||
const inView = useInView(target, { margin: '100% 0% -50% 0%' });
|
||||
|
||||
return (
|
||||
<div
|
||||
className="lg:mt-40 mt-[100px] sm:space-y-3 space-y-10 relative"
|
||||
ref={ref}
|
||||
ref={target}
|
||||
>
|
||||
<Title className="lg:absolute">Награды</Title>
|
||||
<div
|
||||
className="lg:flex sm:grid-cols-2 sm:gap-3 grid w-full h-full gap-2 transition-all duration-500"
|
||||
style={{
|
||||
minHeight:
|
||||
scroll > 400
|
||||
? `calc(740 / 1440 * 100vw)`
|
||||
: scroll < 0
|
||||
? 'calc(640 / 1440 * 100vw)'
|
||||
: `calc((740 - 120 * ${1 - scroll / 400}) / 1440 * 100vw)`,
|
||||
minHeight: !inView ? '50vw' : '44.444vw',
|
||||
}}
|
||||
>
|
||||
{awards.map((award, index) => (
|
||||
|
||||
@@ -14,7 +14,7 @@ import { StatsColumn } from './StatColumn';
|
||||
|
||||
export function Calculator() {
|
||||
const [selectedRegion, setSelectedRegion] = useState<Region>();
|
||||
const [consultations, setConsultations] = useState<number>(10);
|
||||
const [consultations, setConsultations] = useState<number>(60);
|
||||
const [implementationPeriod, setImplementationPeriod] = useState<number>(
|
||||
null!
|
||||
);
|
||||
@@ -215,9 +215,9 @@ export function Calculator() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:flex grid-cols-2 gap-x-3 hidden">
|
||||
<div className="rounded-2xl bg-[linear-gradient(to_top,#7A7A7A40,#7A7A7A30)] p-7 w-1/2 relative overflow-hidden">
|
||||
<p className="text1 font-medium max-w-[60%] mb-11">
|
||||
<div className="md:flex gap-x-3 hidden">
|
||||
<div className="rounded-2xl bg-[linear-gradient(to_top,#7A7A7A40,#7A7A7A30)] p-7 w-1/2 relative overflow-hidden aspect-[514/180] flex flex-col justify-between">
|
||||
<p className="text1 font-medium max-w-[60%]">
|
||||
Срок реализации объекта
|
||||
</p>
|
||||
<p className="font-medium">
|
||||
@@ -269,9 +269,9 @@ export function Calculator() {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-[linear-gradient(to_top,#7A7A7A40,#7A7A7A30)] p-7 w-1/2 relative overflow-hidden">
|
||||
<div className="rounded-2xl bg-[linear-gradient(to_top,#7A7A7A40,#7A7A7A30)] p-7 w-1/2 relative overflow-hidden aspect-[514/180]">
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<p className="text1 mb-11 font-medium">Ежемесячный доход</p>
|
||||
<p className="text1 font-medium">Ежемесячный доход</p>
|
||||
<p className="font-medium">
|
||||
<span className="line2">
|
||||
{calculated ? monthlyIncome : oldMonthlyIncome}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ConsultationRange({
|
||||
start,
|
||||
root.current!.clientWidth - 48
|
||||
),
|
||||
(root.current!.clientWidth - 48) / 35
|
||||
((root.current!.clientWidth - 48) / 35) * 6
|
||||
);
|
||||
el.style.left = `${dx}px`;
|
||||
setOffset(dx);
|
||||
@@ -63,7 +63,7 @@ export function ConsultationRange({
|
||||
<div className="md:space-y-3 md:max-lg:flex-1 space-y-2">
|
||||
<p className="font-medium text-[#7A7A7A] btnl">Консультаций в месяц</p>
|
||||
<div
|
||||
className="lg:py-7 py-5 px-8 bg-[#37393B99] rounded-2xl relative lg:w-[360px] w-full flex justify-end"
|
||||
className="lg:py-[27px] py-5 px-8 bg-[#37393B99] rounded-2xl relative lg:w-[25vw] w-full flex justify-end"
|
||||
ref={root}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -35,7 +35,7 @@ export function RegionSelector({
|
||||
</p>
|
||||
<div
|
||||
ref={root}
|
||||
className="px-8 lg:py-6 py-4 bg-[#37393B99] rounded-2xl flex items-center justify-between lg:w-[360px]"
|
||||
className="px-8 lg:py-6 py-4 bg-[#37393B99] rounded-2xl flex items-center justify-between lg:w-[25vw]"
|
||||
onClick={() => setOpened((prev) => !prev)}
|
||||
>
|
||||
<p className="lg:btnl btnl font-medium">{chosen.name}</p>
|
||||
|
||||
@@ -26,8 +26,8 @@ export function Clients() {
|
||||
className="lg:space-y-16 md:max-lg:space-y-12 space-y-10 lg:mt-40 mt-[100px]"
|
||||
ref={ref}
|
||||
>
|
||||
<div className="flex">
|
||||
<Title className="max-w-2/3 mx-auto text-center">
|
||||
<div className="flex justify-between">
|
||||
<Title className="lg:max-w-2/3a mx-autotext-center">
|
||||
<span className="text-gradient">
|
||||
{companies !== undefined && getCompaniesCount(companies.length)}
|
||||
</span>{' '}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function Slider({
|
||||
<p className="btnl font-medium opacity-60">
|
||||
Последнее в{city.startsWith('В') ? 'о' : ''} {prepositionCity(city)}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex gap-1">
|
||||
{companies.map(({ id, mapIcon, title }, index) => (
|
||||
<button
|
||||
onClick={() => setCurrent(index)}
|
||||
@@ -52,8 +52,8 @@ export function Slider({
|
||||
>
|
||||
<Image
|
||||
src={process.env.NEXT_PUBLIC_S3_BUCKET + mapIcon!}
|
||||
width={48}
|
||||
height={48}
|
||||
width={56}
|
||||
height={56}
|
||||
alt={title}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { StoriesModal } from '@/components/modals/StoriesModal';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { motion, useInView } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
@@ -25,14 +27,14 @@ export function Motivation() {
|
||||
margin: '100% 0px -85% 0px',
|
||||
});
|
||||
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
return (
|
||||
<div className="lg:space-y-[4.444vw] md:space-y-[6.25vw] space-y-[11.111vw]">
|
||||
<Title headerLevel={1} className="sm:max-lg:text-5xla text-center">
|
||||
Помогаем девелоперам
|
||||
<br />
|
||||
продавать недвижимость проще и
|
||||
<Title headerLevel={1} className="text-center">
|
||||
Помогаем девелоперам продавать недвижимость проще и
|
||||
<span className="relative">
|
||||
<span className="text-[#37393B]">быстрее </span>
|
||||
<span className="text-[#37393B]">быстрее </span>
|
||||
<span className="absolute top-[55%] -left-[2.5%] 2xl:h-1.5 h-1 w-full bg-white" />
|
||||
</span>
|
||||
дороже
|
||||
@@ -55,8 +57,11 @@ export function Motivation() {
|
||||
className="rounded-2xl h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="max-md:space-y-[4.444vw]a max-md:flex flex-col justify-between lg:w-[28.611vw] md:max-lg:w-[47.135vw] lg:h-[15.694vw] md:max-lg:h-[29.427vw] w-[46.111vw] h-[61.111vw] lg:p-[1.667vw] p-4 lg:col-start-2 lg:row-start-1 row-start-2 bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] rounded-2xl relative">
|
||||
<p className="font-medium heading2 lg:w-[10.278vw] md:max-lg:w-[19.271vw]">
|
||||
<button
|
||||
onClick={() => setModal(<StoriesModal />, 'stories')}
|
||||
className="flex flex-col justify-between lg:w-[28.611vw] md:max-lg:w-[47.135vw] lg:h-[15.694vw] md:max-lg:h-[29.427vw] w-[46.111vw] h-[61.111vw] lg:p-[1.667vw] p-4 lg:col-start-2 lg:row-start-1 row-start-2 bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] rounded-2xl relative cursor-pointer"
|
||||
>
|
||||
<p className="font-medium heading2 lg:w-[10.278vw] md:max-lg:w-[19.271vw] self-start text-left">
|
||||
Интеграция в офисы продаж
|
||||
</p>
|
||||
<div className="lg:w-[11.667vw] md:max-lg:w-[21.875vw] w-[37.222vw] aspect-square md:absolute relative lg:right-[1.667vw] lg:bottom-[0.903vw] md:max-lg:right-[3.125vw] md:max-lg:bottom-[1.693vw]">
|
||||
@@ -102,7 +107,7 @@ export function Motivation() {
|
||||
className="absolute top-0 text-white rounded-full"
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div className="relative lg:w-[28.611vw] lg:h-[19.028vw] md:max-lg:w-[47.135vw] md:max-lg:h-[29.427vw] overflow-hidden w-[46.111vw] h-[61.111vw] bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] col-start-2 row-start-2 lg:p-[1.667vw] p-4 rounded-2xl flex flex-col lg:gap-[1.944vw] md:max-lg:gap-[3.125vw] gap-[3.333vw]">
|
||||
<p className="heading2 font-medium">Удаленная демонстрация</p>
|
||||
<div className="md:relative absolute lg:w-[19.583vw] md:max-lg:w-[40.885vw] w-[73.333vw] self-center max-md:left-4 max-md:-bottom-0.5">
|
||||
|
||||
@@ -22,22 +22,22 @@ export function IntegrationCRM({ scroll }: { scroll: MotionValue<number> }) {
|
||||
<div className="space-y-15 max-w-9/10">
|
||||
<div className="space-y-4">
|
||||
<div className="flex">
|
||||
<div className="w-14 h-14 rounded-lg bg-[#B5F54E] flex items-center text-black font-medium justify-center -rotate-[4deg] text1 shadow-[0_2px_10px_0_#00000033] z-[2]">
|
||||
<div className="w-16 h-16 rounded-lg bg-[#B5F54E] flex items-center text-black font-medium justify-center -rotate-[4deg] text1 shadow-[0_2px_10px_0_#00000033] z-[2]">
|
||||
2K
|
||||
</div>
|
||||
<div className="bg-[#37393B] rounded-lg right-2 relative shadow-[0_2px_10px_0_#00000033] z-[1]">
|
||||
<Image
|
||||
src={'/img/pages/home/presentation/flat1.png'}
|
||||
width={56}
|
||||
height={56}
|
||||
width={64}
|
||||
height={64}
|
||||
alt="rooms"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-[#37393B] rounded-lg -rotate-[4deg] right-4 relative shadow-[0_2px_10px_0_#00000033]">
|
||||
<Image
|
||||
src={'/img/pages/home/presentation/flat2.png'}
|
||||
width={48}
|
||||
height={48}
|
||||
width={64}
|
||||
height={64}
|
||||
alt="rooms"
|
||||
/>
|
||||
</div>
|
||||
@@ -51,15 +51,15 @@ export function IntegrationCRM({ scroll }: { scroll: MotionValue<number> }) {
|
||||
<div className="flex">
|
||||
<Image
|
||||
src={'/img/pages/home/presentation/vova.png'}
|
||||
width={56}
|
||||
height={56}
|
||||
width={64}
|
||||
height={64}
|
||||
alt="vovka"
|
||||
className="rounded-lg -rotate-[4deg] shadow-[0_2px_10px_0_#00000033] z-[2]"
|
||||
/>
|
||||
<div className="rounded-lg bg-[#37393B] flex justify-center items-center relative right-2 w-14 h-14 shadow-[0_2px_10px_0_#00000033] z-[1]">
|
||||
<div className="rounded-lg bg-[#37393B] flex justify-center items-center relative right-2 w-16 h-16 shadow-[0_2px_10px_0_#00000033] z-[1]">
|
||||
<MailIcon className="text-white w-8 h-8" />
|
||||
</div>
|
||||
<div className="rounded-lg bg-[#37393B] flex justify-center items-center relative right-4 -rotate-[4deg] w-14 h-14 shadow-[0_2px_10px_0_#00000033]">
|
||||
<div className="rounded-lg bg-[#37393B] flex justify-center items-center relative right-4 -rotate-[4deg] w-16 h-16 shadow-[0_2px_10px_0_#00000033]">
|
||||
<PhoneIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function PresentationDesktop() {
|
||||
<Title>
|
||||
Интерактивная презентация{' '}
|
||||
<span className="text-gradient">улучшает опыт выбора недвижимости</span>{' '}
|
||||
и увеличивают темпы продаж квартир в жилом комплексе
|
||||
и увеличивают темпы продаж квартир в жилом комплексе
|
||||
</Title>
|
||||
<div className="h-[480vh]" ref={target}>
|
||||
<div className="flex gap-x-3 items-stretch h-[480vh] max-h-[80vh] overflow-hidden w-full sticky duration-1000 top-24">
|
||||
|
||||
@@ -32,7 +32,7 @@ export function PresentationMini() {
|
||||
|
||||
return (
|
||||
<div className="mt-25 lg:hidden">
|
||||
<div className="md:space-y-12 md:top-30 xs:-top-10 -top-40 sticky space-y-5">
|
||||
<div className="md:space-y-12 md:top-0 xs:-top-10 -top-40 sticky space-y-5">
|
||||
<Title>
|
||||
Интерактивная презентация{' '}
|
||||
<span className="text-gradient">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { search } from '@/consts/presentation/search';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { motion, MotionValue, useTransform } from 'framer-motion';
|
||||
import HeartIcon from '../../../../../public/icons/hearth.svg';
|
||||
import LocationIcon from '../../../../../public/icons/location.svg';
|
||||
|
||||
export function SearchAndSelect({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const opacity = useTransform(scroll, [0, 1 / 6], [1, 0]);
|
||||
@@ -17,30 +18,18 @@ export function SearchAndSelect({ scroll }: { scroll: MotionValue<number> }) {
|
||||
>
|
||||
<div className="md:p-6 md:rounded-2xl md:bg-radial-[at_100%_100%] from-[#7A7A7A66] md:backdrop-blur-[500px] xs:max-md:space-y-4 space-y-2 md:flex flex-col justify-between md:flex-1">
|
||||
<p className="heading2 font-medium">Выбор квартиры на генплане</p>
|
||||
<div className="xs:max-md:space-y-4 space-y-2">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="bg-[#FF4517] rounded-[9px] p-[4.5px]">
|
||||
<Icon
|
||||
name="hearth"
|
||||
color="#232425"
|
||||
svgProp={{
|
||||
className: 'max-md:w-[22.5px] max-md:w-[22.5px] w-5 h-5',
|
||||
}}
|
||||
/>
|
||||
<div className="bg-[#FF4517] rounded-[9px] p-[0.556vw]">
|
||||
<HeartIcon className="text-[#232425] w-[1.389vw] h-[1.389vw]" />
|
||||
</div>
|
||||
<p className="text1 md:max-w-[70%]">
|
||||
Эмоциональное вовлечение пользователя в выбор квартиры
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="bg-[#B5F54E] rounded-[9px] p-[4.5px]">
|
||||
<Icon
|
||||
name="location"
|
||||
color="#232425"
|
||||
svgProp={{
|
||||
className: 'max-md:w-[22.5px] max-md:w-[22.5px] w-5 h-5',
|
||||
}}
|
||||
/>
|
||||
<div className="bg-[#B5F54E] rounded-[9px] p-[0.556vw]">
|
||||
<LocationIcon className="text-[#232425] w-[1.389vw] h-[1.389vw]" />
|
||||
</div>
|
||||
<p className="text1 md:max-w-[70%]">
|
||||
Удобство выбора расположения и видовых характеристик
|
||||
|
||||
@@ -37,13 +37,15 @@ export function ReviewTab({
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<Image
|
||||
src={image}
|
||||
width={96}
|
||||
height={96}
|
||||
alt={title}
|
||||
className="rounded-xl aspect-square"
|
||||
/>
|
||||
<div className="aspect-square">
|
||||
<Image
|
||||
src={image}
|
||||
width={96}
|
||||
height={96}
|
||||
alt={title}
|
||||
className="rounded-xl aspect-square"
|
||||
/>
|
||||
</div>
|
||||
{active && (
|
||||
<motion.div
|
||||
key={index}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function Reviews() {
|
||||
|
||||
const [currentHovered, setCurrentHovered] = useState<number>();
|
||||
|
||||
const isInView = useInView(ref, { margin: '100% 0px -85% 0px' });
|
||||
const isInView = useInView(ref, { margin: '100% 0px -75% 0px' });
|
||||
|
||||
return (
|
||||
<div className="max-lg:space-y-2">
|
||||
@@ -34,10 +34,8 @@ export function Reviews() {
|
||||
}`}
|
||||
>
|
||||
<VideoPlayer src={reviewsData[tab].src} showMutingBtn ref={videoRef}>
|
||||
<div className="lg:space-y-6 space-y-4 lg:max-w-[35.208vw] absolute lg:left-6 lg:top-6 md:max-lg:top-4 left-4 bottom-4 transition-opacity z-[5]">
|
||||
<p className="max-w-[507px] text2 opacity-80">
|
||||
{reviewsData[tab].author}
|
||||
</p>
|
||||
<div className="lg:space-y-6 space-y-4 absolute lg:left-6 lg:top-6 md:max-lg:top-4 left-4 bottom-4 transition-opacity z-[5] max-w-[40vw]">
|
||||
<p className="text2 opacity-80">{reviewsData[tab].author}</p>
|
||||
<p className="accent font-medium">{reviewsData[tab].text}</p>
|
||||
</div>
|
||||
<div className="absolute flex flex-nowrap bottom-6 left-6 gap-2 z-[6] max-lg:hidden">
|
||||
|
||||
@@ -26,12 +26,13 @@ export function Streaming() {
|
||||
<div className="lg:mt-[140px] mt-[100px] lg:space-y-[4.444vw] md:max-lg:space-y-[6.25vw] space-y-[11.111vw]">
|
||||
<Title className="max-md:hidden">
|
||||
Уникальная технология
|
||||
<span className="text-gradient"> удаленной демонстрации</span> дает
|
||||
возможность презентовать объект покупателю из любой точки мира
|
||||
<span className="text-gradient"> удаленной демонстрации</span>{' '}
|
||||
дает возможность презентовать объект покупателю из любой точки
|
||||
мира
|
||||
</Title>
|
||||
<Title headerLevel={2} className="md:hidden text-center">
|
||||
<span className="text-gradient">Удаленная демонстрация —</span>
|
||||
презентуйте объект покупателю из любой точки мира
|
||||
презентуйте объект покупателю из любой точки мира
|
||||
</Title>
|
||||
<div
|
||||
className="lg:grid md:max-lg:flex grid-cols-4 gap-3 px-5 md:-mx-5 md:max-lg:overflow-auto [scrollbar-width:none] relative max-md:aspect-[340/344] transform-3d items-stretch"
|
||||
@@ -68,7 +69,8 @@ export function Streaming() {
|
||||
Расскажем и покажем как это работает на созвоне
|
||||
</p>
|
||||
<Button
|
||||
className="btnl group-hover:scale-105 px-6 py-4 transition-transform rounded-lg"
|
||||
rounded="2xl"
|
||||
className="btnm font-medium group-hover:scale-105 px-6 py-[17px] transition-transform"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Оставить заявку
|
||||
@@ -82,8 +84,9 @@ export function Streaming() {
|
||||
loop
|
||||
autoPlay
|
||||
muted
|
||||
className="lg:apect-[1400/640] md:max-lg:aspect-[736/480] max-md:aspect-[340/600]"
|
||||
>
|
||||
<p className="absolute text-[32px] font-medium bottom-6 left-6 lg:max-w-[40%] md:max-lg:max-w-[80%] accent">
|
||||
<p className="absolute font-medium bottom-6 left-6 lg:max-w-[40%] md:max-lg:max-w-[80%] accent">
|
||||
Graff.estate stream доступен на любых устройствах, для
|
||||
демонстрации нужен только интернет
|
||||
</p>
|
||||
|
||||
@@ -37,12 +37,12 @@ export function StreamingProject({
|
||||
}}
|
||||
/>
|
||||
<div className="lg:self-end space-y-3 font-medium z-1">
|
||||
<p className="heading1">{title}</p>
|
||||
<p className="heading1 font-medium">{title}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{company && (
|
||||
<div className="px-2 py-1.5 flex gap-1 items-center rounded-2xl bg-[#37393B99] btns">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
className="w-2 h-2 rounded-full m-1"
|
||||
style={{ backgroundColor: company.color }}
|
||||
/>
|
||||
{company.title}
|
||||
@@ -53,26 +53,23 @@ export function StreamingProject({
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-4 right-4 lg:hidden">
|
||||
<Button
|
||||
rounded="xl"
|
||||
className="px-3 py-2"
|
||||
icon={<ArrowMoreIcon className="w-4 h-4 text-white" />}
|
||||
>
|
||||
Смотреть
|
||||
</Button>
|
||||
</div>
|
||||
<Link
|
||||
href={href}
|
||||
className="hidden lg:group-hover:block absolute w-full h-full left-0 bottom-0 md:max-lg:rounded-2xl rounded-xl font-medium backdrop-blur-[3px] content-center text-center z-2"
|
||||
className="hidden lg:group-hover:block absolute w-full h-full left-0 bottom-0 md:max-lg:rounded-2xl rounded-xl font-medium backdrop-blur-[3px] content-center text-center"
|
||||
>
|
||||
<p className="btnl flex justify-center gap-2">
|
||||
Начать демонстрацию <ArrowMoreIcon className="text-white w-4 h-4" />
|
||||
</p>
|
||||
</Link>
|
||||
<Link
|
||||
className="lg:group-hover:block hidden bottom-4 right-4 absolute z-2"
|
||||
href={'/'}
|
||||
>
|
||||
<Button
|
||||
icon={<ArrowMoreIcon className="text-white w-4 h-4" />}
|
||||
className="btns px-3 py-2"
|
||||
rounded="xl"
|
||||
>
|
||||
Смотреть
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { IProject } from '@/types/IProject';
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import ArrowMoreIcon from '../../../../public/icons/arrow_more.svg';
|
||||
import { AwardsCard } from './AwardsCard';
|
||||
@@ -25,13 +25,21 @@ export function ProjectsSection({ projects }: { projects: IProject[] }) {
|
||||
<AwardsCard className="sm:col-start-2 sm:row-start-2 row-start-3" />
|
||||
)}
|
||||
{pathname === '/' && (
|
||||
<Link
|
||||
<motion.a
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true, margin: '-100px' }}
|
||||
transition={{
|
||||
duration: 1,
|
||||
ease: [0.58, 0.12, 0.27, 0.98],
|
||||
delay: 0.2,
|
||||
}}
|
||||
href={'/projects'}
|
||||
className="rounded-xl bg-[#232425] aspect-square self-center p-5 opacity-60 hover:opacity-100 transition-opacity flex items-center justify-center btnl font-medium gap-2"
|
||||
>
|
||||
Смотреть все
|
||||
<ArrowMoreIcon className="w-5 h-5 text-white" />
|
||||
</Link>
|
||||
</motion.a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -78,7 +78,7 @@ export function TagsFilters({
|
||||
)}
|
||||
<button
|
||||
onClick={handleApplyClick}
|
||||
className="bg-gradient rounded-2xl btnm flex items-center gap-2 pl-6 py-4 pr-4 font-medium z-[11] -mb-5 mt-5"
|
||||
className="bg-gradient rounded-2xl btnm flex items-center gap-2 pl-6 py-4 pr-4 font-medium z-[11] -mb-5 mt-5 cursor-pointer"
|
||||
>
|
||||
Применить
|
||||
<CheckIcon className="text-white w-4 h-4" />
|
||||
@@ -88,7 +88,7 @@ export function TagsFilters({
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setOpen(true)}
|
||||
className="gap-x-2 rounded-2xl flex items-center pl-6 p-4 bg-[#37393B99] backdrop-blur-sm"
|
||||
className="gap-x-2 rounded-2xl flex items-center pl-6 p-4 bg-[#37393B99] backdrop-blur-sm cursor-pointer"
|
||||
>
|
||||
<p className="btnm font-medium">Фильтры</p>
|
||||
<FilterIcon className="text-white w-4 h-4" />
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useArticleMutation({
|
||||
action === 'create'
|
||||
? await api.post('articles', { json }).json<IArticle>()
|
||||
: await api.put(`articles/${id}`, { json }).json<IArticle>(),
|
||||
onSuccess: async (article) => {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['articles'] });
|
||||
},
|
||||
});
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ export function Button({
|
||||
if (type !== 'submit') e.preventDefault();
|
||||
onClick?.();
|
||||
}}
|
||||
className={`group cursor-pointer relative px-6 py-2 rounded-${
|
||||
rounded ?? 'full'
|
||||
className={`group cursor-pointer relative px-6 py-2${
|
||||
rounded ? ' rounded-' + rounded : ''
|
||||
} min-w-fit ${
|
||||
(color === 'primary'
|
||||
? 'bg-gradient-to-r from-[#798FFF] to-[#D375FF]'
|
||||
|
||||
+1
-3
@@ -27,10 +27,8 @@ export function Option<T extends keyof FieldValues>({
|
||||
<>
|
||||
<label
|
||||
htmlFor={text}
|
||||
className={`cursor-pointer transition-colors rounded-xl font-medium text-nowrap select-none ${
|
||||
className={`cursor-pointer transition-colors rounded-xl font-medium text-nowrap select-none px-6 py-[17px] btnm ${
|
||||
checked ? 'bg-white text-black' : 'bg-[#37393B99]'
|
||||
} ${
|
||||
fieldName === 'products' ? 'px-6 py-[17px] btnm' : 'px-3 py-2 btns'
|
||||
}`}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -58,9 +58,9 @@ export function ProductItem({
|
||||
<div className="relative">
|
||||
<Image
|
||||
src={image}
|
||||
className="rounded-lg !relative min-w-[calc(224/768*100vw)]"
|
||||
className="rounded-lg !relative min-w-[29.167vw]"
|
||||
fill
|
||||
sizes="calc(224/768*100vw)"
|
||||
sizes="29.167vw"
|
||||
alt={text}
|
||||
/>
|
||||
</div>
|
||||
@@ -74,9 +74,9 @@ export function ProductItem({
|
||||
<Image
|
||||
src={image}
|
||||
alt={text}
|
||||
className="rounded-lg !relative min-w-[calc(312/1440*100vw)]"
|
||||
className="rounded-lg !relative min-w-[21.667vw]"
|
||||
fill
|
||||
sizes="calc(312/1440*100vw)"
|
||||
sizes="21.667vw"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export function SlideBadge({
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`flex md:gap-3 gap-1 lg:items-center max-md:flex-col md:max-lg:justify-between md:bg-[#37393B99] rounded-2xl lg:p-1 md:p-2 lg:pr-10 md:backdrop-blur-[20px]${
|
||||
className={`flex md:gap-3 gap-1 lg:items-center max-md:flex-col md:max-lg:justify-between md:bg-[#37393B99] rounded-2xl lg:p-1 md:max-lg:p-2 lg:pr-10 md:backdrop-blur-[20px]${
|
||||
className ? ' ' + className : ''
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -18,7 +18,7 @@ export function StoriesButton() {
|
||||
'stories'
|
||||
)
|
||||
}
|
||||
className="p-0.5 cursor-pointer outline-none rounded-full aspect-square relative first:translate-x-2 last:-translate-x-2 [background:linear-gradient(#2a2c34,#2a2c34)_padding-box,linear-gradient(45deg,#FF52E5,#F6D242)_border-box]"
|
||||
className="p-px cursor-pointer outline-none bg-gradient rounded-full aspect-square relative first:translate-x-2 last:-translate-x-2"
|
||||
key={id}
|
||||
>
|
||||
<Image
|
||||
@@ -26,7 +26,7 @@ export function StoriesButton() {
|
||||
width={52}
|
||||
height={52}
|
||||
alt={title}
|
||||
className="rounded-full object-cover object-center aspect-square"
|
||||
className="rounded-full object-cover object-center aspect-square border-[2px] border-[#0F1011]"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user