tailwind 4 -> 3
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
animation: {
|
||||
"infinite-scroll": "infinite-scroll 45s linear infinite",
|
||||
"highlight-product": "highlight-product 0.1s ease-in 0s",
|
||||
},
|
||||
keyframes: {
|
||||
"infinite-scroll": {
|
||||
from: { transform: "translateX(0%)" },
|
||||
to: { transform: "translateX(-100%)" },
|
||||
},
|
||||
"highlight-product": {
|
||||
"100%": {
|
||||
backgroundImage: "url(/img/components/products/highlight.svg)",
|
||||
},
|
||||
},
|
||||
scaling: {
|
||||
"0%": {
|
||||
transform: "min-width 31.6vw min-height 31.8vw",
|
||||
transition: "transform 500ms",
|
||||
},
|
||||
"100%": {
|
||||
transform: "min-width 48vw min-height 48vw",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
function ({ addBase }) {
|
||||
const preflightStyles = postcss.parse(
|
||||
fs.readFileSync(
|
||||
require.resolve("tailwindcss/lib/css/preflight.css"),
|
||||
"utf8"
|
||||
)
|
||||
);
|
||||
preflightStyles.walkRules((rule) => {
|
||||
rule.selector = ".no-tailwind-base " + rule.selector;
|
||||
});
|
||||
addBase(preflightStyles.nodes);
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name graff.estate;
|
||||
root /var/www/graff.estate/client/dist;
|
||||
|
||||
# SSL
|
||||
ssl_certificate /etc/letsencrypt/live/graff.estate/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/graff.estate/privkey.pem;
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/graff.estate/chain.pem;
|
||||
|
||||
# security
|
||||
include nginxconfig.io/security.conf;
|
||||
|
||||
# logging
|
||||
access_log /var/log/nginx/access.log combined buffer=512k flush=1m;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
# index.html fallback
|
||||
location / {
|
||||
# try_files $uri $uri/ /index.html;
|
||||
try_files $uri $uri.html $uri/ /index.html;
|
||||
}
|
||||
|
||||
|
||||
location /api {
|
||||
rewrite ^/api/(.*)$ /$1 break;
|
||||
proxy_pass http://127.0.0.1:3003;
|
||||
proxy_set_header Host $host;
|
||||
include nginxconfig.io/proxy.conf;
|
||||
}
|
||||
|
||||
# additional config
|
||||
include nginxconfig.io/general.conf;
|
||||
}
|
||||
|
||||
# HTTP redirect
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name graff.estate;
|
||||
include nginxconfig.io/letsencrypt.conf;
|
||||
|
||||
location / {
|
||||
return 301 https://graff.estate$request_uri;
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -1,7 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'export',
|
||||
distDir: 'dist',
|
||||
output: "export",
|
||||
distDir: "dist",
|
||||
reactStrictMode: false,
|
||||
future: { webpack: true },
|
||||
|
||||
@@ -15,7 +15,7 @@ const nextConfig = {
|
||||
}
|
||||
|
||||
const fileLoaderRule = config.module.rules.find((rule) =>
|
||||
rule.test?.test?.('.svg')
|
||||
rule.test?.test?.(".svg")
|
||||
);
|
||||
|
||||
config.module.rules.push(
|
||||
@@ -30,7 +30,7 @@ const nextConfig = {
|
||||
test: /\.svg$/i,
|
||||
issuer: fileLoaderRule.issuer,
|
||||
resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // exclude if *.svg?url
|
||||
use: ['@svgr/webpack'],
|
||||
use: ["@svgr/webpack"],
|
||||
}
|
||||
);
|
||||
|
||||
@@ -39,14 +39,14 @@ const nextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'graff.estate',
|
||||
port: '',
|
||||
protocol: "https",
|
||||
hostname: "graff.estate",
|
||||
port: "",
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'storage.yandexcloud.net',
|
||||
port: '',
|
||||
protocol: "https",
|
||||
hostname: "storage.yandexcloud.net",
|
||||
port: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+3
-4
@@ -13,8 +13,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.5.0",
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
"@tanstack/react-query-devtools": "^5.64.2",
|
||||
"@tinymce/tinymce-react": "^5.1.1",
|
||||
@@ -29,7 +27,6 @@
|
||||
"lenis": "^1.2.1",
|
||||
"libphonenumber-js": "^1.11.7",
|
||||
"next": "14.2.5",
|
||||
"postcss": "^8.5.1",
|
||||
"react": "^18",
|
||||
"react-circular-progressbar": "^2.1.0",
|
||||
"react-dom": "^18",
|
||||
@@ -45,7 +42,6 @@
|
||||
"react-transition-group": "^4.4.5",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sharp": "^0.33.5",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tinymce": "^7.4.1",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zustand": "^4.5.4"
|
||||
@@ -60,8 +56,11 @@
|
||||
"@types/react-phone-number-input": "^3.1.37",
|
||||
"@types/react-rangeslider": "^2.2.7",
|
||||
"@types/react-transition-group": "^4.4.11",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"postcss": "^8.5.4",
|
||||
"tailwindcss": "3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InProcess } from '@/components/pages/InProcess';
|
||||
import { InProcess } from "@/components/pages/InProcess";
|
||||
|
||||
export default function AboutPage() {
|
||||
return <InProcess />;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { api } from '@/api';
|
||||
import { RelevantArticlesPreview } from '@/components/pages/ArticlePage/RelevantArticlesPreview';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import CloseIcon from '../../../../../public/icons/close.svg';
|
||||
import { api } from "@/api";
|
||||
import { RelevantArticlesPreview } from "@/components/pages/ArticlePage/RelevantArticlesPreview";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import CloseIcon from "../../../../../public/icons/close.svg";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -16,7 +16,7 @@ export async function generateMetadata({
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const { cardImage, tags, title } = await queryClient.fetchQuery<IArticle>({
|
||||
queryKey: ['articles', slug],
|
||||
queryKey: ["articles", slug],
|
||||
queryFn: async () => await api.get(`articles/${slug}`).json<IArticle>(),
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function generateMetadata({
|
||||
images: {
|
||||
url: process.env.NEXT_PUBLIC_S3_BUCKET + cardImage,
|
||||
},
|
||||
siteName: 'graff.estate',
|
||||
siteName: "graff.estate",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -45,11 +45,11 @@ export default async function Layout({
|
||||
|
||||
return (
|
||||
<section className="fixed inset-0 bg-[#0F101199] [backdrop-filter:blur(16px)] z-[14]">
|
||||
<Link href={'/blog'} className="inset-0 fixed"></Link>
|
||||
<Link href={"/blog"} className="inset-0 fixed"></Link>
|
||||
<RelevantArticlesPreview slug={slug} />
|
||||
{children}
|
||||
<Link
|
||||
href={'/blog'}
|
||||
href={"/blog"}
|
||||
className="fixed lg:right-[1.389vw] lg:top-[1.389vw] right-5 top-5 lg:rounded-[1.111vw] rounded-2xl bg-[#37393B99] lg:p-[1.111vw] p-4 cursor-pointer"
|
||||
>
|
||||
<CloseIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white" />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { api } from '@/api';
|
||||
import { ArticleSyncPage } from '@/components/pages/ArticlePage/ArticleSyncPage';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { api } from "@/api";
|
||||
import { ArticleSyncPage } from "@/components/pages/ArticlePage/ArticleSyncPage";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import {
|
||||
dehydrate,
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
queryOptions,
|
||||
} from '@tanstack/react-query';
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
export default async function ArticlePage({
|
||||
params,
|
||||
@@ -19,7 +19,7 @@ export default async function ArticlePage({
|
||||
|
||||
await queryClient.prefetchQuery(
|
||||
queryOptions({
|
||||
queryKey: ['articles', slug],
|
||||
queryKey: ["articles", slug],
|
||||
queryFn: () => api.get(`articles/${slug}`).json<IArticle>(),
|
||||
})
|
||||
);
|
||||
@@ -32,11 +32,13 @@ export default async function ArticlePage({
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const queryClient = new QueryClient();
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { staleTime: 0 } },
|
||||
});
|
||||
|
||||
const articles = await queryClient.fetchQuery<IArticle[]>({
|
||||
queryKey: ['articles'],
|
||||
queryFn: () => api.get('articles').json<IArticle[]>(),
|
||||
queryKey: ["articles"],
|
||||
queryFn: () => api.get("articles").json<IArticle[]>(),
|
||||
});
|
||||
|
||||
return articles
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { api } from '@/api';
|
||||
import { ArticlesList } from '@/components/pages/BlogPage/ArticlesList';
|
||||
import { ArticlesPageActions } from '@/components/pages/BlogPage/ArticlesPageActions';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { api } from "@/api";
|
||||
import { ArticlesList } from "@/components/pages/BlogPage/ArticlesList";
|
||||
import { ArticlesPageActions } from "@/components/pages/BlogPage/ArticlesPageActions";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import {
|
||||
dehydrate,
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { Suspense } from 'react';
|
||||
} from "@tanstack/react-query";
|
||||
import { Suspense } from "react";
|
||||
|
||||
export default async function BlogPage() {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['articles'],
|
||||
queryFn: () => api.get('articles').json<IArticle[]>(),
|
||||
queryKey: ["articles"],
|
||||
queryFn: () => api.get("articles").json<IArticle[]>(),
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Feedback } from '@/components/Layout/Feedback';
|
||||
import { Footer } from '@/components/Layout/Footer';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Feedback } from "@/components/Layout/Feedback";
|
||||
import { Footer } from "@/components/Layout/Footer";
|
||||
import dynamic from "next/dynamic";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export default function MainLayout({ children }: PropsWithChildren) {
|
||||
const Header = dynamic(
|
||||
() => import('@/components/Layout/Header').then((mod) => mod.Header),
|
||||
() => import("@/components/Layout/Header").then((mod) => mod.Header),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Header />
|
||||
<main className="flex-1 md:max-lg:pt-[120px] pt-25 md:max-lg:px-4 lg:px-[1.389vw] px-[10px] overflow-clip relative">
|
||||
<main className="flex-1 md:max-lg:pt-[120px] pt-[100px] md:px-4 lg:px-[1.389vw] px-[10px] overflow-clip relative">
|
||||
{children}
|
||||
<Feedback />
|
||||
</main>
|
||||
|
||||
+22
-27
@@ -1,28 +1,23 @@
|
||||
import { api } from '@/api';
|
||||
import { Awards } from '@/components/pages/MainPage/Awards';
|
||||
import { Calculator } from '@/components/pages/MainPage/Calculator/Calculator';
|
||||
import { Clients } from '@/components/pages/MainPage/Clients/Clients';
|
||||
import { Map } from '@/components/pages/MainPage/Map/Map';
|
||||
import { Motivation } from '@/components/pages/MainPage/Motivation';
|
||||
import { Presentation } from '@/components/pages/MainPage/Presentation/Presentation';
|
||||
import { Projects } from '@/components/pages/MainPage/Projects';
|
||||
import { Reviews } from '@/components/pages/MainPage/Reviews/Reviews';
|
||||
import { Statistics } from '@/components/pages/MainPage/Statistics';
|
||||
import { Streaming } from '@/components/pages/MainPage/Streaming/Streaming';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { IProject } from '@/types/IProject';
|
||||
import { api } from "@/api";
|
||||
import { Awards } from "@/components/pages/MainPage/Awards";
|
||||
import { Calculator } from "@/components/pages/MainPage/Calculator/Calculator";
|
||||
import { Motivation } from "@/components/pages/MainPage/Motivation";
|
||||
import { Reviews } from "@/components/pages/MainPage/Reviews/Reviews";
|
||||
import { Statistics } from "@/components/pages/MainPage/Statistics";
|
||||
import { Streaming } from "@/components/pages/MainPage/Streaming/Streaming";
|
||||
import { IProject } from "@/types/IProject";
|
||||
import {
|
||||
dehydrate,
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
queryOptions,
|
||||
} from '@tanstack/react-query';
|
||||
import { Integrations } from '@/components/pages/MainPage/Integrations/Integrations';
|
||||
} from "@tanstack/react-query";
|
||||
import { Integrations } from "@/components/pages/MainPage/Integrations/Integrations";
|
||||
// import { Suspense } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { queryProjectsOptions } from '@/queries/getProjects';
|
||||
import { queryCompaniesOptions } from '@/queries/getCompanies';
|
||||
import { queryCompaniesCountOptions } from '@/queries/getCompaniesCount';
|
||||
import dynamic from "next/dynamic";
|
||||
import { queryProjectsOptions } from "@/queries/getProjects";
|
||||
import { queryCompaniesOptions } from "@/queries/getCompanies";
|
||||
import { queryCompaniesCountOptions } from "@/queries/getCompaniesCount";
|
||||
import { Projects } from "@/components/pages/MainPage/Projects";
|
||||
|
||||
export default async function HomePage() {
|
||||
const queryClient = new QueryClient();
|
||||
@@ -30,8 +25,8 @@ export default async function HomePage() {
|
||||
await queryClient.prefetchQuery(queryProjectsOptions);
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['projects', 'count'],
|
||||
queryFn: () => api.get('projects/count').json<number>(),
|
||||
queryKey: ["projects", "count"],
|
||||
queryFn: () => api.get("projects/count").json<number>(),
|
||||
});
|
||||
|
||||
await queryClient.prefetchQuery(queryCompaniesOptions);
|
||||
@@ -39,27 +34,27 @@ export default async function HomePage() {
|
||||
await queryClient.prefetchQuery(queryCompaniesCountOptions);
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['projects', 'Удаленная демонстрация'],
|
||||
queryKey: ["projects", "Удаленная демонстрация"],
|
||||
queryFn: () =>
|
||||
api.get('projects?tags=Удаленная демонстрация').json<IProject[]>(),
|
||||
api.get("projects?tags=Удаленная демонстрация").json<IProject[]>(),
|
||||
});
|
||||
|
||||
const Presentation = dynamic(
|
||||
() =>
|
||||
import('@/components/pages/MainPage/Presentation/Presentation').then(
|
||||
import("@/components/pages/MainPage/Presentation/Presentation").then(
|
||||
(mod) => mod.Presentation
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const Map = dynamic(
|
||||
() => import('@/components/pages/MainPage/Map/Map').then((mod) => mod.Map),
|
||||
() => import("@/components/pages/MainPage/Map/Map").then((mod) => mod.Map),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const Clients = dynamic(
|
||||
() =>
|
||||
import('@/components/pages/MainPage/Clients/Clients').then(
|
||||
import("@/components/pages/MainPage/Clients/Clients").then(
|
||||
(mod) => mod.Clients
|
||||
),
|
||||
{ ssr: false }
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { InProcess } from '@/components/pages/InProcess';
|
||||
// import { PrimeDesktopPage } from '@/components/pages/PrimePage/PrimePage';
|
||||
// import PrimePageMobile from '@/components/pages/PrimePageMobile/PrimePageMobile';
|
||||
import { PrimeDesktopPage } from "@/components/pages/PrimePage/PrimePage";
|
||||
import PrimePageMobile from "@/components/pages/PrimePageMobile/PrimePageMobile";
|
||||
|
||||
export default function PrimePage() {
|
||||
// return (
|
||||
// <div className="relative">
|
||||
// <PrimePageMobile />
|
||||
// <PrimeDesktopPage />
|
||||
// </div>
|
||||
// );
|
||||
return <InProcess />;
|
||||
return (
|
||||
<div className="relative">
|
||||
<PrimePageMobile />
|
||||
<PrimeDesktopPage />
|
||||
</div>
|
||||
);
|
||||
// return <InProcess />;
|
||||
}
|
||||
|
||||
+33
-41
@@ -1,5 +1,7 @@
|
||||
@import url('/fonts/TTHovesProAll/stylesheet.css');
|
||||
@import 'tailwindcss';
|
||||
@import url("/fonts/TTHovesProAll/stylesheet.css");
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@theme {
|
||||
--gradient: linear-gradient(87deg, #798fff 15%, #d375ff 100%);
|
||||
@@ -10,7 +12,7 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'TTHovesPro';
|
||||
font-family: "TTHovesPro";
|
||||
color: #fff;
|
||||
background-color: #0f1011;
|
||||
}
|
||||
@@ -21,7 +23,7 @@ html {
|
||||
}
|
||||
|
||||
.bg-gradient-card {
|
||||
content: '';
|
||||
content: "";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@@ -46,67 +48,61 @@ html {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
@utility line1 {
|
||||
@apply 2xl:text-[128px] lg:max-2xl:text-[clamp(96px,96px+(100vw-1440px)/96*32,128px)] md:max-lg:text-[clamp(56px,56px+(100vw-768px)/672*40,96px)] xs:max-md:text-[clamp(40px,40px+(100vw-360px)/408*16,56px)] text-[40px] leading-[85%];
|
||||
@layer utilities {
|
||||
.line1 {
|
||||
@apply 2xl:text-[128px] lg:text-[clamp(96px,96px+(100vw-1440px)/96*32,128px)] md:text-[clamp(56px,56px+(100vw-768px)/672*40,96px)] xs: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)] xs:max-md:text-[clamp(32px,32px+(100vw-360px)/408*8,40px)] max-xs:text-[32px] leading-[95%];
|
||||
.line2 {
|
||||
@apply lg:text-[clamp(64px,4.444vw,88px)] md:text-[clamp(40px,40px+(100vw-768px)/672*24,64px)] xs:text-[clamp(32px,32px+(100vw-360px)/408*8,40px)] max-xs:text-[32px] leading-[95%];
|
||||
}
|
||||
|
||||
@utility heading1 {
|
||||
@apply lg:text-[clamp(28px,1.944vw,42px)] md:max-lg:text-[clamp(24px,24px+(100vw-768px)/672*4,28px)] text-2xl leading-[1.167];
|
||||
.heading1 {
|
||||
@apply lg:text-[clamp(28px,1.944vw,42px)] md:text-[clamp(24px,24px+(100vw-768px)/672*4,28px)] text-2xl leading-[1.167];
|
||||
}
|
||||
|
||||
@utility heading2 {
|
||||
@apply lg:text-[clamp(24px,1.667vw,36px)] md:max-lg:text-[clamp(20px,20px+(100vw-768px)/672*4,24px)] xs:max-md:text-[clamp(16px,16px+(100vw-360px)/408*4,20px)] text-base lg:leading-[1.2] leading-[1.125];
|
||||
.heading2 {
|
||||
@apply lg:text-[clamp(24px,1.667vw,36px)] md:text-[clamp(20px,20px+(100vw-768px)/672*4,24px)] xs:text-[clamp(16px,16px+(100vw-360px)/408*4,20px)] text-base lg:leading-[1.2] leading-[1.125];
|
||||
}
|
||||
|
||||
@utility accent {
|
||||
@apply lg:text-[clamp(32px,2.222vw,56px)] md:max-lg:text-[clamp(24px,24px+(100vw-768px)/672*8,32px)] text-2xl lg:leading-[1.1] leading-none;
|
||||
.accent {
|
||||
@apply lg:text-[clamp(32px,2.222vw,56px)] md:text-[clamp(24px,24px+(100vw-768px)/672*8,32px)] text-2xl lg:leading-[1.1] leading-none;
|
||||
}
|
||||
|
||||
@utility text1 {
|
||||
@apply lg:text-[clamp(18px,1.25vw,24px)] md:max-lg:text-[clamp(14px,14px+(100vw-768px)/672*4,18px)] text-sm leading-[1.35];
|
||||
.text1 {
|
||||
@apply lg:text-[clamp(18px,1.25vw,24px)] md:text-[clamp(14px,14px+(100vw-768px)/672*4,18px)] text-sm leading-[1.35];
|
||||
}
|
||||
|
||||
@utility text2 {
|
||||
@apply lg:text-[clamp(12px,0.972vw,20px)] md:max-lg:text-[clamp(12px,12px+(100vw-768px)/672*2,14px)] text-xs leading-[1.35];
|
||||
.text2 {
|
||||
@apply lg:text-[clamp(12px,0.972vw,20px)] md:text-[clamp(12px,12px+(100vw-768px)/672*2,14px)] text-xs leading-[1.35];
|
||||
}
|
||||
|
||||
@utility btnl {
|
||||
@apply lg:text-[clamp(18px,1.25vw,28px)] md:max-lg:text-[clamp(16px,16+(100vw-768px)/672*2,18px)] text-base leading-none;
|
||||
.btnl {
|
||||
@apply lg:text-[clamp(18px,1.25vw,28px)] md:text-[clamp(16px,16+(100vw-768px)/256*2,18px)] text-base leading-none;
|
||||
}
|
||||
|
||||
@utility btnm {
|
||||
@apply lg:text-[clamp(16px,1.111vw,24px)] md:max-lg:text-[clamp(14px,14px+(100vw-768px)/672*2,16px)] text-sm leading-none;
|
||||
.btnm {
|
||||
@apply lg:text-[clamp(16px,1.111vw,24px)] md:text-[clamp(14px,14px+(100vw-768px)/256*2,16px)] text-sm leading-none;
|
||||
}
|
||||
|
||||
@utility btns {
|
||||
@apply lg:text-[clamp(14px,0.972vw,20px)] md:max-lg:text-[clamp(12px,12px+(100vw-768px)/672*2,14px)] text-xs leading-none;
|
||||
.btns {
|
||||
@apply lg:text-[clamp(14px,0.972vw,20px)] md:text-[clamp(12px,12px+(100vw-768px)/256*2,14px)] text-xs leading-none;
|
||||
}
|
||||
|
||||
@utility caption {
|
||||
.caption {
|
||||
@apply text-[clamp(14px,14px+(100vw-360px)/1240*2,16px)] leading-none;
|
||||
}
|
||||
|
||||
@utility text-gradient {
|
||||
/* -webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
@apply bg-gradient-to-r from-[#798FFF] to-[#D375FF] bg-clip-text; */
|
||||
.text-gradient {
|
||||
background: linear-gradient(87deg, #798fff 15%, #d375ff 100%);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
@utility bg-gradient {
|
||||
.bg-gradient {
|
||||
background: linear-gradient(87deg, #798fff 15%, #d375ff 100%);
|
||||
}
|
||||
|
||||
@theme {
|
||||
--breakpoint-lg: 1440px;
|
||||
--breakpoint-xs: 360px;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -130,7 +126,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* @layer components {
|
||||
.h1 {
|
||||
@apply -tracking-[.02em] 2xl:text-[clamp(96px,96px+(100vw-1560px)/360*16,112px)] text-[clamp(40px,40px+(100vw-360px)/1240*56,96px)] leading-[clamp(36px,36px+(100vw-360px)/1240*50.4,86.4px)] font-medium;
|
||||
}
|
||||
@@ -147,10 +143,6 @@ body {
|
||||
@apply 2xl:text-[clamp(20px,20px+(100vw-1560px)/360*8,28px)] text-[clamp(16px,16px+(100vw-360px)/1240*4,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 2xl:text-[clamp(20px,20px+(100vw-1560px)/360*4,24px)] text-[clamp(16px,16px+(100vw-360px)/1240*4,20px)] leading-[clamp(21.6px,21.6px+(100vw-360px)/1240*5.4,27px)];
|
||||
}
|
||||
@@ -225,7 +217,7 @@ body {
|
||||
.card-gradient-5 {
|
||||
@apply bg-[radial-gradient(circle_closest-side_at_center,#5545AC,transparent)] bg-[length:0px_0px] hover:bg-[length:100%_100%] bg-center bg-no-repeat transition-all duration-300 delay-100;
|
||||
}
|
||||
/* */
|
||||
|
||||
.line1 {
|
||||
@apply min-[1440px]:text-[clamp(96px,96px+(100vw-1440px)/480*32,128px)] md:text-[clamp(92px,92px+(100vw-768px)/672*8,100px)] sm:text-[clamp(40px,40px+(100vw-360px)/408*16,56px)] text-[40px] leading-[85%];
|
||||
}
|
||||
@@ -274,7 +266,7 @@ body {
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
@keyframes custom-ping {
|
||||
0% {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { api } from "@/api";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import { MetadataRoute } from "next";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const baseUrl = "https://graff.estate";
|
||||
|
||||
// Статические страницы
|
||||
const staticPages: MetadataRoute.Sitemap = [
|
||||
{
|
||||
url: baseUrl,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/projects`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/prime`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/blog`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/about`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/web`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/stream`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/picture`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/walk`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.7,
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
// Получаем все статьи блога
|
||||
const articles = await api.get("articles").json<IArticle[]>();
|
||||
|
||||
const blogPages: MetadataRoute.Sitemap = articles
|
||||
.filter(({ slug, drafted }) => slug && !drafted)
|
||||
.map(({ slug, createdAt }) => ({
|
||||
url: `${baseUrl}/blog/${slug}`,
|
||||
lastModified: createdAt ? new Date(createdAt) : new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
}));
|
||||
|
||||
return [...staticPages.slice(0, 4), ...blogPages, ...staticPages.slice(4)];
|
||||
} catch (error) {
|
||||
console.error("Error generating sitemap:", error);
|
||||
// Возвращаем хотя бы статические страницы, если API недоступен
|
||||
return staticPages;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
import { api } from '@/api';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { api } from "@/api";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||
import Dropzone from "react-dropzone";
|
||||
import {
|
||||
FieldValues,
|
||||
Path,
|
||||
PathValue,
|
||||
useFormContext,
|
||||
useWatch,
|
||||
} from 'react-hook-form';
|
||||
import AddIcon from '../../public/icons/add.svg';
|
||||
import RestartIcon from '../../public/icons/restart.svg';
|
||||
import TrashIcon from '../../public/icons/trash.svg';
|
||||
} from "react-hook-form";
|
||||
import AddIcon from "../../public/icons/add.svg";
|
||||
import RestartIcon from "../../public/icons/restart.svg";
|
||||
import TrashIcon from "../../public/icons/trash.svg";
|
||||
|
||||
export function ImageUploader<T extends FieldValues>({
|
||||
dest,
|
||||
name,
|
||||
label = 'Выберите или перетащите изображение',
|
||||
className = '',
|
||||
label = "Выберите или перетащите изображение",
|
||||
className = "",
|
||||
required = false,
|
||||
}: {
|
||||
dest: string;
|
||||
@@ -27,7 +28,7 @@ export function ImageUploader<T extends FieldValues>({
|
||||
className?: string;
|
||||
}) {
|
||||
const [file, setFile] = useState<File>();
|
||||
const [previewFile, setPreviewFile] = useState('');
|
||||
const [previewFile, setPreviewFile] = useState("");
|
||||
|
||||
const { register, setValue, control } = useFormContext();
|
||||
|
||||
@@ -44,12 +45,12 @@ export function ImageUploader<T extends FieldValues>({
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('dest', dest);
|
||||
formData.append('files', file);
|
||||
formData.append("dest", dest);
|
||||
formData.append("files", file);
|
||||
|
||||
try {
|
||||
const filePaths = await api
|
||||
.post('upload', { body: formData })
|
||||
.post("upload", { body: formData })
|
||||
.json<PathValue<T, Path<T>>[]>();
|
||||
setValue(name, filePaths[0]!);
|
||||
} catch (error) {
|
||||
@@ -71,19 +72,19 @@ export function ImageUploader<T extends FieldValues>({
|
||||
setFile(file);
|
||||
setPreviewFile(URL.createObjectURL(file));
|
||||
}}
|
||||
accept={{ 'image/*': ['*'] }}
|
||||
accept={{ "image/*": ["*"] }}
|
||||
noClick
|
||||
>
|
||||
{({ getRootProps, getInputProps, inputRef }) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`relative border border-[#37393B] px-3 py-2 rounded-lg flex flex-col justify-center items-center gap-2${
|
||||
className ? ' ' + className : ''
|
||||
className ? " " + className : ""
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept={'image/*'}
|
||||
accept={"image/*"}
|
||||
required={required}
|
||||
{...{ ...register(name, { required }), ref: inputRef }}
|
||||
onChange={handleChangeFile}
|
||||
@@ -93,7 +94,7 @@ export function ImageUploader<T extends FieldValues>({
|
||||
<>
|
||||
<div
|
||||
className={`relative${
|
||||
name === 'image' ? ' max-w-[calc(292/824*100%)]' : ''
|
||||
name === "image" ? " max-w-[calc(292/824*100%)]" : ""
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
@@ -101,9 +102,9 @@ export function ImageUploader<T extends FieldValues>({
|
||||
previewFile ||
|
||||
process.env.NEXT_PUBLIC_S3_BUCKET + currentImg
|
||||
}
|
||||
alt={''}
|
||||
alt={""}
|
||||
className={`pointer-events-none aspect-square object-${
|
||||
name === 'image' ? 'cover rounded-2xl' : 'contain'
|
||||
name === "image" ? "cover rounded-2xl" : "contain"
|
||||
} !relative`}
|
||||
sizes="100%"
|
||||
/>
|
||||
@@ -125,7 +126,7 @@ export function ImageUploader<T extends FieldValues>({
|
||||
e.preventDefault();
|
||||
setFile(undefined!);
|
||||
setValue(name, undefined!);
|
||||
setPreviewFile('');
|
||||
setPreviewFile("");
|
||||
}}
|
||||
className="bg-[#37393B99] px-3 py-2 rounded-xl cursor-pointer"
|
||||
>
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { IProject } from '@/types/IProject';
|
||||
import { IStory } from '@/types/IStory';
|
||||
import { isArticle } from '@/utils/isArticle';
|
||||
import { isCompany } from '@/utils/isCompany';
|
||||
import { isProject } from '@/utils/isProject';
|
||||
import { isStory } from '@/utils/isStory';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import EditIcon from '../../public/icons/edit.svg';
|
||||
import TrashIcon from '../../public/icons/trash.svg';
|
||||
import { DeleteItemModal } from './DeleteItemModal';
|
||||
import { ArticleContentFormModal } from './modals/ArticleContentFormModal';
|
||||
import { CompanyFormModal } from './modals/CompanyFormModal';
|
||||
import { MapPointProjectFormModal } from './modals/MapPointFormModal';
|
||||
import { ProjectFormModal } from './modals/ProjectFormModal';
|
||||
import { StoryFormModal } from './modals/StoryFormModal';
|
||||
import { IMapProject } from '@/types/IMapProject';
|
||||
import { useCheckAuthQuery } from "@/queries/checkAuth";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import { ICompany } from "@/types/ICompany";
|
||||
import { IProject } from "@/types/IProject";
|
||||
import { IStory } from "@/types/IStory";
|
||||
import { isArticle } from "@/utils/isArticle";
|
||||
import { isCompany } from "@/utils/isCompany";
|
||||
import { isProject } from "@/utils/isProject";
|
||||
import { isStory } from "@/utils/isStory";
|
||||
import { SyntheticEvent } from "react";
|
||||
import EditIcon from "../../public/icons/edit.svg";
|
||||
import TrashIcon from "../../public/icons/trash.svg";
|
||||
import { DeleteItemModal } from "./DeleteItemModal";
|
||||
// import { ArticleContentFormModal } from './modals/ArticleContentFormModal';
|
||||
import { CompanyFormModal } from "./modals/CompanyFormModal";
|
||||
import { MapPointProjectFormModal } from "./modals/MapPointFormModal";
|
||||
import { ProjectFormModal } from "./modals/ProjectFormModal";
|
||||
import { StoryFormModal } from "./modals/StoryFormModal";
|
||||
import { IMapProject } from "@/types/IMapProject";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const ArticleContentFormModal = dynamic(
|
||||
() =>
|
||||
import("./modals/ArticleContentFormModal").then(
|
||||
(mod) => mod.ArticleContentFormModal
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export function ItemActions({
|
||||
item,
|
||||
@@ -42,10 +51,10 @@ export function ItemActions({
|
||||
setModal(<CompanyFormModal action="edit" defaultValues={company} />);
|
||||
} else if (isArticle(item)) setModal(<ArticleContentFormModal {...item} />);
|
||||
else if (isStory(item))
|
||||
setModal(<StoryFormModal action={'edit'} defaultValues={item} />);
|
||||
setModal(<StoryFormModal action={"edit"} defaultValues={item} />);
|
||||
else
|
||||
setModal(
|
||||
<MapPointProjectFormModal action={'edit'} defaultValues={item} />
|
||||
<MapPointProjectFormModal action={"edit"} defaultValues={item} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,34 +64,34 @@ export function ItemActions({
|
||||
<DeleteItemModal
|
||||
id={item.id}
|
||||
title={
|
||||
'Удаление ' +
|
||||
"Удаление " +
|
||||
(isProject(item)
|
||||
? 'проекта'
|
||||
? "проекта"
|
||||
: isCompany(item)
|
||||
? 'компании'
|
||||
? "компании"
|
||||
: isArticle(item)
|
||||
? 'статьи'
|
||||
? "статьи"
|
||||
: isStory(item)
|
||||
? 'истории'
|
||||
: 'проекта на карте')
|
||||
? "истории"
|
||||
: "проекта на карте")
|
||||
}
|
||||
entity={
|
||||
isProject(item)
|
||||
? 'projects'
|
||||
? "projects"
|
||||
: isCompany(item)
|
||||
? 'companies'
|
||||
? "companies"
|
||||
: isArticle(item)
|
||||
? 'articles'
|
||||
? "articles"
|
||||
: isStory(item)
|
||||
? 'stories'
|
||||
: 'map'
|
||||
? "stories"
|
||||
: "map"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="group-hover:opacity-100 absolute top-0 left-0 z-11 flex gap-1 p-4 transition-opacity opacity-0">
|
||||
<div className="group-hover:opacity-100 absolute top-0 left-0 z-[11] flex gap-1 p-4 transition-opacity opacity-0">
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="relative lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#37393B99] backdrop-blur-sm rounded-full outline-none cursor-pointer"
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { api } from '@/api';
|
||||
import { projectsTags } from '@/consts/projectsTags';
|
||||
import { Product } from '@/types/Product';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { CheckboxesGroup } from '@/ui/CheckboxesGroup';
|
||||
import { getExampleNumber } from 'libphonenumber-js';
|
||||
import examples from 'libphonenumber-js/mobile/examples';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import ReactInputMask from 'react-input-mask';
|
||||
import { Country } from 'react-phone-number-input';
|
||||
import CheckIcon from '../../../public/icons/check.svg';
|
||||
import { api } from "@/api";
|
||||
import { projectsTags } from "@/consts/projectsTags";
|
||||
import { Product } from "@/types/Product";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { CheckboxesGroup } from "@/ui/CheckboxesGroup";
|
||||
import { getExampleNumber } from "libphonenumber-js";
|
||||
import examples from "libphonenumber-js/mobile/examples";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ChangeEvent, useMemo, useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import ReactInputMask from "react-input-mask";
|
||||
import { Country } from "react-phone-number-input";
|
||||
import CheckIcon from "../../../public/icons/check.svg";
|
||||
|
||||
export function Feedback() {
|
||||
const pathname = usePathname();
|
||||
@@ -22,8 +22,8 @@ export function Feedback() {
|
||||
<div
|
||||
id="contacts"
|
||||
className={`lg:mb-20 md:mb-12 lg:flex lg:gap-[0.833vw] max-lg:space-y-12 justify-between lg:mt-[14.07vh]${
|
||||
!pathname.startsWith('/form') ? ' mt-25' : ''
|
||||
} mb-10${pathname.startsWith('/prime') ? ' max-lg:hidden' : ''}`}
|
||||
!pathname.startsWith("/form") ? " mt-[100px]" : ""
|
||||
} mb-10`}
|
||||
>
|
||||
<h2 className="line2 font-medium max-lg:mb-6 lg:max-w-[45%]">
|
||||
<span className="text-[#7A7A7A]">Хотите увеличить конверсию?</span>
|
||||
@@ -45,15 +45,15 @@ interface IInput {
|
||||
export function FeedbackForm() {
|
||||
const [[phoneCode, country], setPhoneCodeAndCountry] = useState<
|
||||
[string, Country]
|
||||
>(['+7', 'RU']);
|
||||
>(["+7", "RU"]);
|
||||
|
||||
const placeholder = useMemo(
|
||||
() =>
|
||||
getExampleNumber(country, examples)
|
||||
?.formatInternational()
|
||||
.split(' ')
|
||||
.split(" ")
|
||||
.slice(1)
|
||||
.join(' '),
|
||||
.join(" "),
|
||||
[country]
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ export function FeedbackForm() {
|
||||
const { register, handleSubmit, formState } = form;
|
||||
|
||||
async function onSubmit(data: IInput) {
|
||||
await api.post('mail', { json: data }).json();
|
||||
await api.post("mail", { json: data }).json();
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -86,7 +86,7 @@ export function FeedbackForm() {
|
||||
type="text"
|
||||
required
|
||||
placeholder="Имя*"
|
||||
{...register('fullname')}
|
||||
{...register("fullname")}
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<input
|
||||
@@ -95,7 +95,7 @@ export function FeedbackForm() {
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Email*"
|
||||
{...register('email')}
|
||||
{...register("email")}
|
||||
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<div className="flex gap-x-3 py-2 border-[#3D425C] relative">
|
||||
@@ -103,15 +103,21 @@ export function FeedbackForm() {
|
||||
type="tel"
|
||||
autoComplete="none"
|
||||
onChange={(e) => {
|
||||
console.log(e.nativeEvent.type);
|
||||
if (e.nativeEvent.type.startsWith("input")) {
|
||||
form.setValue(
|
||||
'phone',
|
||||
phoneCode + e.target.value.replaceAll(/ /g, '')
|
||||
"phone",
|
||||
((e.nativeEvent as InputEvent)?.inputType !==
|
||||
"insertFromPaste"
|
||||
? phoneCode
|
||||
: "") + e.target.value.replaceAll(/ /g, "")
|
||||
);
|
||||
}
|
||||
}}
|
||||
id={'tel'}
|
||||
id={"tel"}
|
||||
maskChar={null}
|
||||
mask={'+7 ' + (placeholder?.replace(/\d/g, '9') ?? '')}
|
||||
placeholder={'+7 ' + placeholder}
|
||||
mask={"+7 " + (placeholder?.replace(/\d/g, "9") ?? "")}
|
||||
placeholder={"+7 " + placeholder}
|
||||
className="placeholder:btnl placeholder:font-medium placeholder:select-none peer btnl w-full h-full transition-all bg-transparent rounded-none outline-none"
|
||||
/>
|
||||
<div className="bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2" />
|
||||
@@ -124,13 +130,13 @@ export function FeedbackForm() {
|
||||
Оставить заявку
|
||||
</Button>
|
||||
<Link
|
||||
href={'/policy'}
|
||||
href={"/policy"}
|
||||
className="text2 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1"
|
||||
>
|
||||
<p>
|
||||
<span className="text-[#7A7A7A]">
|
||||
*Нажимая кнопку отправить, вы принимаете
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
условия использования и политику конфиденциальности
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
@@ -37,7 +37,7 @@ export function Footer() {
|
||||
<ContactLink href="https://rutube.ru/channel/25505040">
|
||||
<RutubeIcon className="lg:w-[1.389vw] lg:h-[1.389vw] md:max-lg:w-[2.083vw] md:max-lg:h-[2.083vw] w-[5.556vw] h-[5.556vw] text-white group-hover:text-black" />
|
||||
</ContactLink>
|
||||
<ContactLink href="https://vk.com/graff.interactive">
|
||||
<ContactLink href="https://vk.com/graffinteractive?from=groups">
|
||||
<VkIcon className="lg:w-[1.389vw] lg:h-[1.389vw] md:max-lg:w-[2.083vw] md:max-lg:h-[2.083vw] w-[5.556vw] h-[5.556vw] text-white group-hover:text-black" />
|
||||
</ContactLink>
|
||||
<ContactLink href="https://www.youtube.com/@GRAFFtech">
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { api } from '@/api';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { useScroll } from '@/hooks/useScroll';
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { HeaderLink } from '@/ui/HeaderLink';
|
||||
import {
|
||||
QueryClient,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import BurgerIcon from '../../../public/icons/burger.svg';
|
||||
import CloseIcon from '../../../public/icons/close.svg';
|
||||
import GraffIcon from '../../../public/icons/graff.svg';
|
||||
import LogoIcon from '../../../public/icons/logo_hor.svg';
|
||||
import SkolkovoIcon from '../../../public/icons/skolkovo.svg';
|
||||
import { Products } from './Products';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
import { getQueryClient } from '@/lib/queryClient';
|
||||
import { api } from "@/api";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
import { useCheckAuthQuery } from "@/queries/checkAuth";
|
||||
import { HeaderLink } from "@/ui/HeaderLink";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useRef, useState } from "react";
|
||||
import { useOnClickOutside } from "usehooks-ts";
|
||||
import BurgerIcon from "../../../public/icons/burger.svg";
|
||||
import CloseIcon from "../../../public/icons/close.svg";
|
||||
import GraffIcon from "../../../public/icons/graff.svg";
|
||||
import LogoIcon from "../../../public/icons/logo_hor.svg";
|
||||
import SkolkovoIcon from "../../../public/icons/skolkovo.svg";
|
||||
import { Products } from "./Products";
|
||||
import { useAuthStore } from "@/stores/useAuthStore";
|
||||
import { getQueryClient } from "@/lib/queryClient";
|
||||
|
||||
export function Header() {
|
||||
const { setToken } = useAuthStore();
|
||||
@@ -33,9 +29,9 @@ export function Header() {
|
||||
const { data: auth } = useCheckAuthQuery();
|
||||
|
||||
const { mutate: logout } = useMutation({
|
||||
mutationFn: () => api.get('auth/logout').json<{ success: boolean }>(),
|
||||
mutationFn: () => api.get("auth/logout").json<{ success: boolean }>(),
|
||||
onSuccess() {
|
||||
queryClient.invalidateQueries({ queryKey: ['checkAuth'] });
|
||||
queryClient.invalidateQueries({ queryKey: ["checkAuth"] });
|
||||
setToken(null);
|
||||
},
|
||||
});
|
||||
@@ -67,7 +63,7 @@ export function Header() {
|
||||
return (
|
||||
<header className="lg:mt-[1.389vw] relative flex lg:px-[1.389vw]">
|
||||
<Link
|
||||
href={'/'}
|
||||
href={"/"}
|
||||
ref={logoRef}
|
||||
className="max-lg:hidden cursor-pointer outline-none"
|
||||
>
|
||||
@@ -78,13 +74,13 @@ export function Header() {
|
||||
<motion.nav
|
||||
ref={navRef}
|
||||
animate={{
|
||||
width: burgerOpened && (isXs || isSm) ? 340 : 'auto',
|
||||
width: burgerOpened && (isXs || isSm) ? 340 : "auto",
|
||||
}}
|
||||
className="fixed self-center max-lg:top-4 top-[1.389vw] lg:p-[0.278vw] p-1 lg:rounded-[1.389vw] rounded-[20px] bg-[#37393B99] [backdrop-filter:blur(40px)] lg:gap-[0.278vw] flex gap-1 z-12 mx-auto left-1/2 -translate-x-1/2"
|
||||
className="fixed self-center max-lg:top-4 top-[1.389vw] lg:p-[0.278vw] p-1 lg:rounded-[1.389vw] rounded-[20px] bg-[#37393B99] [backdrop-filter:blur(40px)] lg:gap-[0.278vw] flex gap-1 z-[12] mx-auto left-1/2 -translate-x-1/2"
|
||||
>
|
||||
{((isLg && scroll < -logoRef.current?.clientHeight!) || !isLg) && (
|
||||
<Link
|
||||
href={'/'}
|
||||
href={"/"}
|
||||
className="aspect-square lg:p-[1.111vw] p-3 hover:bg-[#232425] group active:bg-white lg:rounded-[1.111vw] rounded-2xl content-center m-auto"
|
||||
>
|
||||
<GraffIcon className="text-white group-active:text-black lg:w-[1.111vw] md:max-lg:w-[2.083vw] w-4 lg:h-[1.111vw] md:max-lg:h-[2.083vw] h-4" />
|
||||
@@ -96,10 +92,10 @@ export function Header() {
|
||||
>
|
||||
<button
|
||||
className={
|
||||
'lg:px-[1.667vw] lg:py-[1.111vw] px-6 py-4 font-medium btnm lg:rounded-[1.111vw] rounded-2xl active:bg-white cursor-pointer outline-none' +
|
||||
"lg:px-[1.667vw] lg:py-[1.111vw] px-6 py-4 font-medium btnm lg:rounded-[1.111vw] rounded-2xl active:bg-white cursor-pointer outline-none" +
|
||||
(productsOpened
|
||||
? ' bg-white text-black'
|
||||
: ' active:text-black hover:bg-[#232425]')
|
||||
? " bg-white text-black"
|
||||
: " active:text-black hover:bg-[#232425]")
|
||||
}
|
||||
onClick={() => setProductsOpened((prev) => !prev)}
|
||||
>
|
||||
@@ -108,22 +104,22 @@ export function Header() {
|
||||
</div>
|
||||
<HeaderLink
|
||||
className="max-md:hidden btnm"
|
||||
href={'/about'}
|
||||
text={'О нас'}
|
||||
href={"/about"}
|
||||
text={"О нас"}
|
||||
/>
|
||||
<HeaderLink
|
||||
className="max-md:hidden btnm"
|
||||
href={'/blog'}
|
||||
text={'Блог'}
|
||||
href={"/blog"}
|
||||
text={"Блог"}
|
||||
/>
|
||||
<HeaderLink
|
||||
className="max-md:hidden btnm"
|
||||
href={'/projects'}
|
||||
text={'Проекты'}
|
||||
href={"/projects"}
|
||||
text={"Проекты"}
|
||||
/>
|
||||
<div className="md:justify-end flex justify-center flex-1">
|
||||
<Link
|
||||
href={'/form'}
|
||||
href={"/form"}
|
||||
className="btnm bg-gradient font-medium lg:px-[1.667vw] lg:py-[1.181vw] py-[17px] px-6 text-nowrap lg:rounded-[1.111vw] rounded-2xl flex items-center"
|
||||
>
|
||||
Оставить заявку
|
||||
@@ -162,18 +158,18 @@ export function Header() {
|
||||
>
|
||||
<div className="px-2 -mx-4 space-y-1">
|
||||
<HeaderLink
|
||||
href={'/about'}
|
||||
text={'О нас'}
|
||||
href={"/about"}
|
||||
text={"О нас"}
|
||||
className="accent"
|
||||
/>
|
||||
<HeaderLink
|
||||
href={'/blog'}
|
||||
text={'Блог'}
|
||||
href={"/blog"}
|
||||
text={"Блог"}
|
||||
className="accent"
|
||||
/>
|
||||
<HeaderLink
|
||||
href={'/projects'}
|
||||
text={'Проекты'}
|
||||
href={"/projects"}
|
||||
text={"Проекты"}
|
||||
className="accent"
|
||||
/>
|
||||
</div>
|
||||
@@ -185,7 +181,7 @@ export function Header() {
|
||||
<div>
|
||||
<p className="btnm opacity-60 mb-1">Контакты:</p>
|
||||
<Link
|
||||
href={'tel:88007700067'}
|
||||
href={"tel:88007700067"}
|
||||
className="accent font-medium outline-none"
|
||||
>
|
||||
8 800 770 00 67
|
||||
@@ -206,7 +202,7 @@ export function Header() {
|
||||
ref={productsRef}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
width: isLg ? 'max(68.889vw,360px)' : 'calc(100vw - 32px)',
|
||||
width: isLg ? "max(68.889vw,360px)" : "calc(100vw - 32px)",
|
||||
opacity: 100,
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -219,13 +215,13 @@ export function Header() {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{pathname.startsWith('/projects') ? (
|
||||
{pathname.startsWith("/projects") ? (
|
||||
<Link
|
||||
href={'https://dprofile.ru/graff.estate'}
|
||||
href={"https://dprofile.ru/graff.estate"}
|
||||
className="max-xl:hidden rounded-[20px] bg-[#37393B99] backdrop-blur-[20px] hover:bg-[#232425] py-5 pl-[63px] pr-[33px] right-5 fixed z-[2] top-5 overflow-clip bg-[url(/img/components/header/dp.png)] bg-no-repeat bg-right-bottom"
|
||||
>
|
||||
<img
|
||||
src={'/img/components/header/show_case.png'}
|
||||
src={"/img/components/header/show_case.png"}
|
||||
width={53.77}
|
||||
height={104.48}
|
||||
alt="кейс dprofile"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { useEffect } from 'react';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function ModalContainer() {
|
||||
const { modal, setModal } = useModalStore();
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setModal(null);
|
||||
if (e.key === "Escape") setModal(null);
|
||||
};
|
||||
document.addEventListener('keydown', listener);
|
||||
document.addEventListener("keydown", listener);
|
||||
|
||||
return () => document.removeEventListener('keydown', listener);
|
||||
return () => document.removeEventListener("keydown", listener);
|
||||
}, [setModal]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import products from '@/consts/products.json';
|
||||
import { ProductItem } from '@/ui/ProductItem';
|
||||
import products from "@/consts/products.json";
|
||||
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)] lg:w-[68.889vw]a lg:h-[38.889vw] amd:max-lg:w-[95.703vw] md:max-lg:h-[72.917vw] h-full w-full">
|
||||
<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:h-[38.889vw] md:h-[72.917vw] h-full w-full">
|
||||
{products.map((product, index) => (
|
||||
<ProductItem
|
||||
href={'/' + product.title.toLowerCase()}
|
||||
href={"/" + product.title.toLowerCase()}
|
||||
key={product.id}
|
||||
{...product}
|
||||
className={
|
||||
index < 2
|
||||
? 'max-md:aspect-[100/114] 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-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'
|
||||
? "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-child(3)]:row-start-1 md:[&:nth-child(3)]:row-end-3 md:[&:nth-child(4)]:row-start-3 md:[&:nth-child(4)]:row-end-5"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { api } from '@/api';
|
||||
import { useFieldArrayFormContext } from '@/lib/FieldArrayFormProvider';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Dispatch, SetStateAction, useRef } from 'react';
|
||||
import { FieldArrayWithId, useController } from 'react-hook-form';
|
||||
import { Editor } from 'tinymce';
|
||||
import { IArticleInput } from '../modals/ArticleFormModal';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { api } from "@/api";
|
||||
import { useFieldArrayFormContext } from "@/lib/FieldArrayFormProvider";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { Dispatch, SetStateAction, useEffect, useRef } from "react";
|
||||
import { FieldArrayWithId, useController } from "react-hook-form";
|
||||
import { Editor } from "tinymce";
|
||||
import { IArticleInput } from "../modals/ArticleFormModal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { BundledEditor } from "@/lib/BundledEditor";
|
||||
|
||||
export function ArticleContentEditor({
|
||||
index,
|
||||
@@ -16,7 +17,7 @@ export function ArticleContentEditor({
|
||||
}: {
|
||||
index: number;
|
||||
setEditing: Dispatch<SetStateAction<boolean>>;
|
||||
item: FieldArrayWithId<IArticleInput, 'blocks', 'id'> & { content: string };
|
||||
item: FieldArrayWithId<IArticleInput, "blocks", "id"> & { content: string };
|
||||
}) {
|
||||
const ref = useRef<Editor | null>(null);
|
||||
const videoUploadRef = useRef<HTMLInputElement>(null);
|
||||
@@ -31,10 +32,10 @@ export function ArticleContentEditor({
|
||||
defaultValue: item.content,
|
||||
});
|
||||
|
||||
const BundledEditor = dynamic(
|
||||
() => import('@/lib/BundledEditor').then((mod) => mod.BundledEditor),
|
||||
{ ssr: false }
|
||||
);
|
||||
// const BundledEditor = dynamic(
|
||||
// () => import("@/lib/BundledEditor").then((mod) => mod.BundledEditor),
|
||||
// { ssr: false }
|
||||
// );
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-4">
|
||||
@@ -47,7 +48,7 @@ export function ArticleContentEditor({
|
||||
}}
|
||||
init={{
|
||||
content_style:
|
||||
'body {color: #fff; background: #14161f; font-size:16px; height: 100vh}',
|
||||
"body {color: #fff; background: #14161f; font-size:16px; height: 100vh}",
|
||||
video_template_callback: (data: { source: string }) =>
|
||||
'<video src="' +
|
||||
data.source +
|
||||
@@ -55,34 +56,34 @@ export function ArticleContentEditor({
|
||||
images_upload_credentials: true,
|
||||
images_upload_handler: async (blobInfo) => {
|
||||
const formData = new FormData();
|
||||
formData.append('files', blobInfo.blob(), blobInfo.filename());
|
||||
formData.append('dest', 'blog');
|
||||
formData.append("files", blobInfo.blob(), blobInfo.filename());
|
||||
formData.append("dest", "blog");
|
||||
const res = await api
|
||||
.post('upload', { body: formData })
|
||||
.post("upload", { body: formData })
|
||||
.json<string[]>();
|
||||
return process.env.NEXT_PUBLIC_S3_BUCKET + res[0];
|
||||
},
|
||||
font_size_formats: '10px 12px 14px 16px 18px 20px 24px 28px 30px',
|
||||
file_picker_types: 'image media',
|
||||
font_size_formats: "10px 12px 14px 16px 18px 20px 24px 28px 30px",
|
||||
file_picker_types: "image media",
|
||||
file_picker_callback: async (cb) => {
|
||||
videoUploadRef.current!.onchange = async function () {
|
||||
const reader = new FileReader();
|
||||
const file = videoUploadRef.current?.files?.[0];
|
||||
reader.onload = async function () {
|
||||
const id = 'blobId' + new Date().getTime();
|
||||
const id = "blobId" + new Date().getTime();
|
||||
const blobCache = ref.current?.editorUpload.blobCache;
|
||||
const base64 = reader.result?.toString().split(',')[1];
|
||||
const base64 = reader.result?.toString().split(",")[1];
|
||||
const blobInfo = blobCache?.create(id, file!, base64!);
|
||||
blobCache?.add(blobInfo!);
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
'files',
|
||||
"files",
|
||||
blobInfo?.blob()!,
|
||||
blobInfo?.filename()
|
||||
);
|
||||
formData.append('dest', 'blog');
|
||||
formData.append("dest", "blog");
|
||||
const res = await api
|
||||
.post('upload', { body: formData })
|
||||
.post("upload", { body: formData })
|
||||
.json<{ files: string[] }>();
|
||||
|
||||
cb(process.env.NEXT_PUBLIC_S3_BUCKET + res.files[0], {
|
||||
@@ -95,22 +96,22 @@ export function ArticleContentEditor({
|
||||
},
|
||||
automatic_uploads: true,
|
||||
plugins: [
|
||||
'anchor',
|
||||
'autolink',
|
||||
'charmap',
|
||||
'codesample',
|
||||
'emoticons',
|
||||
'image',
|
||||
'link',
|
||||
'lists',
|
||||
'media',
|
||||
'nonbreaking',
|
||||
'preview',
|
||||
'save',
|
||||
'searchreplace',
|
||||
'table',
|
||||
'visualblocks',
|
||||
'wordcount',
|
||||
"anchor",
|
||||
"autolink",
|
||||
"charmap",
|
||||
"codesample",
|
||||
"emoticons",
|
||||
"image",
|
||||
"link",
|
||||
"lists",
|
||||
"media",
|
||||
"nonbreaking",
|
||||
"preview",
|
||||
"save",
|
||||
"searchreplace",
|
||||
"table",
|
||||
"visualblocks",
|
||||
"wordcount",
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Reorder } from 'framer-motion';
|
||||
import parse from 'html-react-parser';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FieldArrayWithId, useFormContext } from 'react-hook-form';
|
||||
import { BlockActions } from '../BlockActions';
|
||||
import { IArticleInput } from '../modals/ArticleFormModal';
|
||||
import { ArticleContentEditor } from './ArticleContentEditor';
|
||||
import { Reorder } from "framer-motion";
|
||||
import parse from "html-react-parser";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { FieldArrayWithId, useFormContext } from "react-hook-form";
|
||||
import { BlockActions } from "../BlockActions";
|
||||
import { IArticleInput } from "../modals/ArticleFormModal";
|
||||
import { ArticleContentEditor } from "./ArticleContentEditor";
|
||||
|
||||
export interface IArticleContentInputProps {
|
||||
index: number;
|
||||
item: FieldArrayWithId<IArticleInput, 'blocks', 'id'> & { content: string };
|
||||
item: FieldArrayWithId<IArticleInput, "blocks", "id"> & { content: string };
|
||||
}
|
||||
|
||||
export function ArticleContentInput({
|
||||
@@ -19,7 +25,7 @@ export function ArticleContentInput({
|
||||
}: IArticleContentInputProps) {
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editing, setEditing] = useState(true);
|
||||
|
||||
const [content, setContent] = useState(item.content);
|
||||
|
||||
@@ -27,9 +33,9 @@ export function ArticleContentInput({
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = watch(({ blocks }) => {
|
||||
if (!blocks || !blocks.length || blocks[index]?.type !== 'Content')
|
||||
if (!blocks || !blocks.length || blocks[index]?.type !== "Content")
|
||||
return;
|
||||
setContent(blocks[index].content ?? '');
|
||||
setContent(blocks[index].content ?? "");
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [index, watch]);
|
||||
@@ -38,6 +44,7 @@ export function ArticleContentInput({
|
||||
<Reorder.Item
|
||||
as="div"
|
||||
value={item}
|
||||
key={item.id}
|
||||
className="lg:col-start-2 lg:col-span-2 sm:col-span-3 col-span-full relative flex items-start gap-4"
|
||||
onDoubleClick={() => ref.current && ref.current.click()}
|
||||
>
|
||||
@@ -49,7 +56,7 @@ export function ArticleContentInput({
|
||||
/>
|
||||
) : (
|
||||
<div className="border-[#3D425C] border p-4 rounded-3xl [&_p_*]:!text1 [&_h1_*]:!heading1 [&_h2_*]:!heading2">
|
||||
{item.type === 'Content' && parse(content)}
|
||||
{item.type === "Content" && parse(content)}
|
||||
</div>
|
||||
)}
|
||||
<BlockActions
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useArticleMutation } from '@/hooks/useArticleMutation';
|
||||
import { FieldArrayFormProvider } from '@/lib/FieldArrayFormProvider';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { reorderFields } from '@/utils/reorderFields';
|
||||
import { Reorder } from 'framer-motion';
|
||||
import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
|
||||
import AddIcon from '../../../public/icons/add.svg';
|
||||
import EditIcon from '../../../public/icons/edit.svg';
|
||||
import { ArticleButtonLinkInput } from '../articleInputs/ArticleButtonLinkInput';
|
||||
import { ArticleContentInput } from '../articleInputs/ArticleContentInput';
|
||||
import { ArticleImageInput } from '../articleInputs/ArticleImageInput';
|
||||
import { ArticleQuoteInput } from '../articleInputs/ArticleQuoteInput';
|
||||
import { ArticleSliderInput } from '../articleInputs/ArticleSliderInput';
|
||||
import { ArticleVideoUploader } from '../articleInputs/ArticleVideoUploader';
|
||||
import { ArticleFormActions } from './ArticleFormActions';
|
||||
import { ArticleFormModal, IArticleInput } from './ArticleFormModal';
|
||||
import { useArticleMutation } from "@/hooks/useArticleMutation";
|
||||
import { FieldArrayFormProvider } from "@/lib/FieldArrayFormProvider";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { reorderFields } from "@/utils/reorderFields";
|
||||
import { Reorder } from "framer-motion";
|
||||
import { FormProvider, useFieldArray, useForm } from "react-hook-form";
|
||||
import AddIcon from "../../../public/icons/add.svg";
|
||||
import EditIcon from "../../../public/icons/edit.svg";
|
||||
import { ArticleButtonLinkInput } from "../articleInputs/ArticleButtonLinkInput";
|
||||
import { ArticleContentInput } from "../articleInputs/ArticleContentInput";
|
||||
import { ArticleImageInput } from "../articleInputs/ArticleImageInput";
|
||||
import { ArticleQuoteInput } from "../articleInputs/ArticleQuoteInput";
|
||||
import { ArticleSliderInput } from "../articleInputs/ArticleSliderInput";
|
||||
import { ArticleVideoUploader } from "../articleInputs/ArticleVideoUploader";
|
||||
import { ArticleFormActions } from "./ArticleFormActions";
|
||||
import { ArticleFormModal, IArticleInput } from "./ArticleFormModal";
|
||||
|
||||
export function ArticleContentFormModal({
|
||||
posterImage,
|
||||
@@ -47,17 +47,17 @@ export function ArticleContentFormModal({
|
||||
|
||||
const fieldArrayMethods = useFieldArray({
|
||||
control,
|
||||
name: 'blocks',
|
||||
name: "blocks",
|
||||
});
|
||||
|
||||
const { append, fields, swap } = fieldArrayMethods;
|
||||
|
||||
const { mutateAsync: save } = useArticleMutation({ action: 'edit', id });
|
||||
const { mutateAsync: save } = useArticleMutation({ action: "edit", id });
|
||||
|
||||
async function handleSave(drafted: boolean) {
|
||||
await save({
|
||||
...getValues(),
|
||||
blocks: JSON.stringify(getValues('blocks')),
|
||||
blocks: JSON.stringify(getValues("blocks")),
|
||||
drafted,
|
||||
});
|
||||
setModal(null);
|
||||
@@ -66,17 +66,17 @@ export function ArticleContentFormModal({
|
||||
return (
|
||||
<>
|
||||
<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="relative space-y-4 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] h-[calc(100vh-40px)] z-10 overflow-y-auto">
|
||||
<div className="top-3 right-4 absolute">
|
||||
<Button
|
||||
className="bg-[#37393B99] z-3 backdrop-blur-sm p-4 btnm lg:rounded-[1.111vw] rounded-2xl"
|
||||
className="bg-[#37393B99] z-[3] backdrop-blur-sm p-4 btnm lg:rounded-[1.111vw] rounded-2xl"
|
||||
color="secondary"
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<ArticleFormModal
|
||||
action="edit"
|
||||
defaultValues={{
|
||||
blocks: getValues('blocks') ?? [],
|
||||
blocks: getValues("blocks") ?? [],
|
||||
tags,
|
||||
title,
|
||||
cardImage,
|
||||
@@ -96,7 +96,7 @@ export function ArticleContentFormModal({
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="bg-no-repeat bg-cover bg-top px-[75px] pb-6 z-2 w-full aspect-[954/261] relative flex items-end justify-between gap-4 before:absolute before:left-0 before:top-0 before:w-full before:h-full before:-z-1 before:aspect-[954/261] before:bg-gradient-to-t before:from-[#00000099]"
|
||||
className="bg-no-repeat bg-cover bg-top px-[75px] pb-6 z-[2] w-full aspect-[954/261] relative flex items-end justify-between gap-4 before:absolute before:left-0 before:top-0 before:w-full before:h-full before:-z-1 before:aspect-[954/261] before:bg-gradient-to-t before:from-[#00000099]"
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
process.env.NEXT_PUBLIC_S3_BUCKET + posterImage
|
||||
@@ -125,31 +125,31 @@ export function ArticleContentFormModal({
|
||||
className="py-10 px-[75px] space-y-5"
|
||||
>
|
||||
{fields.map((item, index) =>
|
||||
item.type === 'Content' ? (
|
||||
item.type === "Content" ? (
|
||||
<ArticleContentInput
|
||||
key={item.id}
|
||||
index={index}
|
||||
item={item}
|
||||
/>
|
||||
) : item.type === 'Quote' ? (
|
||||
) : item.type === "Quote" ? (
|
||||
<ArticleQuoteInput
|
||||
key={item.id}
|
||||
index={index}
|
||||
item={item}
|
||||
/>
|
||||
) : item.type === 'Slider' ? (
|
||||
) : item.type === "Slider" ? (
|
||||
<ArticleSliderInput
|
||||
key={item.id}
|
||||
index={index}
|
||||
item={item}
|
||||
/>
|
||||
) : item.type === 'Video' ? (
|
||||
) : item.type === "Video" ? (
|
||||
<ArticleVideoUploader
|
||||
key={item.id}
|
||||
item={item}
|
||||
index={index}
|
||||
/>
|
||||
) : item.type === 'ButtonLink' ? (
|
||||
) : item.type === "ButtonLink" ? (
|
||||
<ArticleButtonLinkInput
|
||||
key={item.id}
|
||||
index={index}
|
||||
@@ -171,7 +171,7 @@ export function ArticleContentFormModal({
|
||||
<div className="fixed bottom-5 left-[84.028vw] z-10 space-y-2">
|
||||
<button
|
||||
className="flex items-center gap-2 lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#232425] lg:rounded-[0.833vw] rounded-xl cursor-pointer hover:bg-[#24252699] btns active:bg-white active:text-black group"
|
||||
onClick={() => append({ type: 'Content', content: '' })}
|
||||
onClick={() => append({ type: "Content", content: "" })}
|
||||
>
|
||||
<AddIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white group-active:text-black" />
|
||||
Абзац
|
||||
@@ -180,11 +180,11 @@ export function ArticleContentFormModal({
|
||||
className="flex items-center gap-2 lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#232425] lg:rounded-[0.833vw] rounded-xl cursor-pointer hover:bg-[#24252699] btns active:bg-white active:text-black group"
|
||||
onClick={() =>
|
||||
append({
|
||||
type: 'Quote',
|
||||
avatar: '',
|
||||
name: '',
|
||||
position: '',
|
||||
text: '',
|
||||
type: "Quote",
|
||||
avatar: "",
|
||||
name: "",
|
||||
position: "",
|
||||
text: "",
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -193,28 +193,28 @@ export function ArticleContentFormModal({
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-2 lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#232425] lg:rounded-[0.833vw] rounded-xl cursor-pointer hover:bg-[#24252699] btns active:bg-white active:text-black group"
|
||||
onClick={() => append({ type: 'Video', src: '' })}
|
||||
onClick={() => append({ type: "Video", src: "" })}
|
||||
>
|
||||
<AddIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white group-active:text-black" />
|
||||
Видео
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-2 lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#232425] lg:rounded-[0.833vw] rounded-xl cursor-pointer hover:bg-[#24252699] btns active:bg-white active:text-black group"
|
||||
onClick={() => append({ type: 'Image', img: '' })}
|
||||
onClick={() => append({ type: "Image", img: "" })}
|
||||
>
|
||||
<AddIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white group-active:text-black" />
|
||||
Картинка
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-2 lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#232425] lg:rounded-[0.833vw] rounded-xl cursor-pointer hover:bg-[#24252699] btns active:bg-white active:text-black group"
|
||||
onClick={() => append({ type: 'Slider', images: [] })}
|
||||
onClick={() => append({ type: "Slider", images: [] })}
|
||||
>
|
||||
<AddIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white group-active:text-black" />
|
||||
Слайдер
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-2 lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 bg-[#232425] lg:rounded-[0.833vw] rounded-xl cursor-pointer hover:bg-[#24252699] btns active:bg-white active:text-black group"
|
||||
onClick={() => append({ type: 'ButtonLink', title: '', link: '' })}
|
||||
onClick={() => append({ type: "ButtonLink", title: "", link: "" })}
|
||||
>
|
||||
<AddIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white group-active:text-black" />
|
||||
Кнопка
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useArticleMutation } from '@/hooks/useArticleMutation';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { IArticle } from '@/types/IArticle';
|
||||
import { CheckboxesGroup } from '@/ui/CheckboxesGroup';
|
||||
import { TextInput } from '@/ui/TextInput';
|
||||
import { FormProvider, useForm, useWatch } from 'react-hook-form';
|
||||
import { ImageUploader } from '../ImageUploader';
|
||||
import { ArticleContentFormModal } from './ArticleContentFormModal';
|
||||
import { ArticleFormActions } from './ArticleFormActions';
|
||||
import { FormModalHeader } from './FormModalHeader';
|
||||
import ReactLenis, { LenisRef } from 'lenis/react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLenis } from '@/hooks/useLenis';
|
||||
import { useArticleMutation } from "@/hooks/useArticleMutation";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import { CheckboxesGroup } from "@/ui/CheckboxesGroup";
|
||||
import { TextInput } from "@/ui/TextInput";
|
||||
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { ImageUploader } from "../ImageUploader";
|
||||
// import { ArticleContentFormModal } from "./ArticleContentFormModal";
|
||||
import { ArticleFormActions } from "./ArticleFormActions";
|
||||
import { FormModalHeader } from "./FormModalHeader";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
interface IArticleFormModalProps<TAction extends 'create' | 'edit'> {
|
||||
const ArticleContentFormModal = dynamic(
|
||||
() =>
|
||||
import("./ArticleContentFormModal").then(
|
||||
(mod) => mod.ArticleContentFormModal
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface IArticleFormModalProps<TAction extends "create" | "edit"> {
|
||||
action: TAction;
|
||||
defaultValues?: TAction extends 'edit' ? IArticle : undefined;
|
||||
defaultValues?: TAction extends "edit" ? IArticle : undefined;
|
||||
}
|
||||
|
||||
export interface IArticleInput extends Omit<IArticle, 'id'> {}
|
||||
export interface IArticleInput extends Omit<IArticle, "id"> {}
|
||||
|
||||
export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
export function ArticleFormModal<TAction extends "create" | "edit">({
|
||||
action,
|
||||
defaultValues,
|
||||
}: IArticleFormModalProps<TAction>) {
|
||||
@@ -34,7 +40,7 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
tags: [],
|
||||
drafted: true,
|
||||
},
|
||||
mode: 'onChange',
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
async function onSubmit(data: IArticleInput) {
|
||||
@@ -53,7 +59,7 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
await mutateAsync({
|
||||
...getValues(),
|
||||
blocks: JSON.stringify(
|
||||
defaultValues ? defaultValues.blocks : getValues('blocks') ?? []
|
||||
defaultValues ? defaultValues.blocks : getValues("blocks") ?? []
|
||||
),
|
||||
drafted,
|
||||
});
|
||||
@@ -61,7 +67,7 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
}
|
||||
|
||||
const { mutateAsync } = useArticleMutation(
|
||||
action === 'create'
|
||||
action === "create"
|
||||
? { action, id: undefined }
|
||||
: { action, id: defaultValues!.id }
|
||||
);
|
||||
@@ -101,7 +107,7 @@ export function ArticleFormModal<TAction extends 'create' | 'edit'>({
|
||||
</label>
|
||||
<CheckboxesGroup
|
||||
name="tags"
|
||||
options={['Недвижимость', 'Награды', 'Выставки']}
|
||||
options={["Недвижимость", "Награды", "Выставки"]}
|
||||
/>
|
||||
</div>
|
||||
<ImageUploader
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { api } from '@/api';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { TextInput } from '@/ui/TextInput';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import CloseIcon from '../../../public/icons/close.svg';
|
||||
import { ImageUploader } from '../ImageUploader';
|
||||
import { FormModalHeader } from './FormModalHeader';
|
||||
import ReactLenis from 'lenis/react';
|
||||
import { useLenis } from '@/hooks/useLenis';
|
||||
import { api } from "@/api";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { ICompany } from "@/types/ICompany";
|
||||
import { TextInput } from "@/ui/TextInput";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
|
||||
import CloseIcon from "../../../public/icons/close.svg";
|
||||
import { ImageUploader } from "../ImageUploader";
|
||||
import { FormModalHeader } from "./FormModalHeader";
|
||||
|
||||
interface ICompanyFormInput {
|
||||
title: string;
|
||||
@@ -19,14 +17,14 @@ interface ICompanyFormInput {
|
||||
logo?: string;
|
||||
}
|
||||
|
||||
interface ICompanyFormModalProps<TAction extends 'create' | 'edit'> {
|
||||
interface ICompanyFormModalProps<TAction extends "create" | "edit"> {
|
||||
action: TAction;
|
||||
defaultValues?: TAction extends 'edit'
|
||||
defaultValues?: TAction extends "edit"
|
||||
? ICompanyFormInput & { id: string }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function CompanyFormModal<TAction extends 'create' | 'edit'>({
|
||||
export function CompanyFormModal<TAction extends "create" | "edit">({
|
||||
action,
|
||||
defaultValues,
|
||||
}: ICompanyFormModalProps<TAction>) {
|
||||
@@ -40,17 +38,15 @@ export function CompanyFormModal<TAction extends 'create' | 'edit'>({
|
||||
|
||||
const { mutateAsync } = useMutation<ICompany, Error, ICompanyFormInput>({
|
||||
mutationFn: async (json) =>
|
||||
action === 'create'
|
||||
? await api.post('companies', { json }).json()
|
||||
action === "create"
|
||||
? await api.post("companies", { json }).json()
|
||||
: await api.put(`companies/${defaultValues?.id}`, { json }).json(),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['companies'] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["companies"] });
|
||||
setModal(null);
|
||||
},
|
||||
});
|
||||
|
||||
// const lenis = useLenis();
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -59,10 +55,7 @@ export function CompanyFormModal<TAction extends 'create' | 'edit'>({
|
||||
>
|
||||
<CloseIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white" />
|
||||
</button>
|
||||
<div
|
||||
// ref={lenis}
|
||||
className="relative space-y-10 py-10 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] max-h-[calc(100vh-40px)] pl-[75px] pr-[55px] overflow-y-auto z-[1]"
|
||||
>
|
||||
<div className="relative space-y-10 py-10 bg-[#232425] rounded-[28px] top-5 w-[calc(954/1440*100vw)] max-h-[calc(100vh-40px)] pl-[75px] pr-[55px] overflow-y-auto z-[1]">
|
||||
<FormModalHeader
|
||||
disabled={false}
|
||||
submitHandler={form.handleSubmit(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import CloseIcon from '../../../public/icons/close.svg';
|
||||
import TelegramIcon from '../../../public/icons/tg.svg';
|
||||
import { useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { usePopupStore } from '@/stores/usePopupStore';
|
||||
import CloseIcon from "../../../public/icons/close.svg";
|
||||
import TelegramIcon from "../../../public/icons/tg.svg";
|
||||
import { useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { usePopupStore } from "@/stores/usePopupStore";
|
||||
|
||||
function Popup() {
|
||||
const { isShowPopup, setIsShowPopup } = usePopupStore();
|
||||
@@ -29,25 +29,25 @@ function Popup() {
|
||||
{isShowPopup && (
|
||||
<motion.div
|
||||
animate={{ y: -40 }}
|
||||
transition={{ type: 'spring' }}
|
||||
className="fixed bottom-0 sm:left-10 left-1/2 max-md:-translate-x-1/2 z-30 w-[320px] lg:w-[22.222vw] p-px lg:p-[0.069vw] rounded-2xl bg-gradient"
|
||||
transition={{ type: "spring" }}
|
||||
className="fixed bottom-0 md:left-10 left-[calc((100vw-320px)/2)] z-[30] w-[320px] lg:min-w-80 lg:w-[16vw] p-px lg:p-[0.069vw] rounded-2xl bg-gradient"
|
||||
>
|
||||
<div className="bg-[#14161F] rounded-2xl p-4 flex flex-col gap-y-5 lg:gap-y-10">
|
||||
<p className="btns font-medium leading-[15.4px]">
|
||||
Новости разработки интерактивных решений для девелоперов в нашем
|
||||
<p className="btns font-medium">
|
||||
Новости разработки интерактивных решений для девелоперов в нашем
|
||||
телеграм канале
|
||||
</p>
|
||||
<Link
|
||||
href={'https://t.me/graffestate'}
|
||||
href={"https://t.me/graffestate"}
|
||||
target="_blank"
|
||||
onClick={handlePopupClick}
|
||||
className="flex items-center gap-x-1 w-full justify-center font-semibold btns bg-gradient rounded-full py-2 hover:opacity-80 transition-opacity"
|
||||
className="gap-x-1 btns bg-gradient hover:opacity-80 flex items-center justify-center w-full py-2 font-semibold transition-opacity rounded-full"
|
||||
>
|
||||
<TelegramIcon className="lg:w-[1.667vw] lg:h-[1.667vw] w-4 h-4" />
|
||||
Перейти
|
||||
</Link>
|
||||
<button
|
||||
className="absolute top-2 right-2 hover:bg-white rounded-full flex p-px hover:bg-opacity-10 group transition-colors"
|
||||
className="top-2 right-2 hover:bg-white hover:bg-opacity-10 group absolute flex p-px transition-colors rounded-full cursor-pointer"
|
||||
onClick={handlePopupClick}
|
||||
>
|
||||
<CloseIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 m-auto group-hover:text-black transition-colors" />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { useGetStories } from '@/queries/getStories';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { createRef, RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { useSwipeable } from 'react-swipeable';
|
||||
import CloseIcon from '../../../public/icons/close.svg';
|
||||
import { ItemActions } from '../ItemActions';
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useGetStories } from "@/queries/getStories";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { createRef, RefObject, useEffect, useRef, useState } from "react";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import CloseIcon from "../../../public/icons/close.svg";
|
||||
import { ItemActions } from "../ItemActions";
|
||||
|
||||
export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -39,9 +39,9 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
const handleTimeUpdate = () =>
|
||||
setCurrentProgress(video.currentTime / video.duration);
|
||||
|
||||
video.addEventListener('timeupdate', handleTimeUpdate);
|
||||
video.addEventListener("timeupdate", handleTimeUpdate);
|
||||
|
||||
return () => video.removeEventListener('timeupdate', handleTimeUpdate);
|
||||
return () => video.removeEventListener("timeupdate", handleTimeUpdate);
|
||||
}, [currentIndex, videoRefs]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -90,7 +90,7 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
</button>
|
||||
<div
|
||||
{...handlers}
|
||||
className="overflow-hidden z-1 md:m-auto lg:w-[83.958vw] lg:h-[76.433vh] md:max-lg:w-[157.422vw] md:max-lg:h-[67.669vh] max-md:w-screen h-dvh max-md:top-0"
|
||||
className="overflow-hidden z-[1] md:m-auto lg:w-[83.958vw] lg:h-[76.433vh] md:max-lg:w-[157.422vw] md:max-lg:h-[67.669vh] max-md:w-screen h-dvh max-md:top-0"
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
@@ -110,8 +110,8 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
key={id}
|
||||
className={`select-none relative flex items-end group overflow-hidden lg:rounded-[0.833vw] md:max-lg:rounded-xl cursor-pointer transition-transform lg:p-[1.111vw] 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] max-md:min-w-screen max-md:h-dvh'
|
||||
: 'lg:min-w-[26.25vw] lg:h-[71.338vh] md:max-lg:min-w-[49.219vw] md:max-lg:h-[63.158vh] max-md:min-w-screen max-md:h-dvh'
|
||||
? "lg:min-w-[28.125vw] lg:h-[76.433vh] md:max-lg:min-w-[52.734vw] md:max-lg:h-[67.669vh] max-md:min-w-screen max-md:h-dvh"
|
||||
: "lg:min-w-[26.25vw] lg:h-[71.338vh] md:max-lg:min-w-[49.219vw] md:max-lg:h-[63.158vh] max-md:min-w-screen max-md:h-dvh"
|
||||
}`}
|
||||
onClick={() => setCurrentIndex(index)}
|
||||
>
|
||||
@@ -125,17 +125,17 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
<div
|
||||
className={`absolute bottom-0 left-1/2 -translate-x-1/2 w-full h-full lg:rounded-[0.833vw] md:max-lg: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-[radial-gradient(149.167vw_33.906vh_at_bottom,#6078F2,#C868F5,transparent)]'
|
||||
: 'md: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]"
|
||||
}`}
|
||||
/>
|
||||
{currentIndex === index && (
|
||||
<div className="space-y-5 z-1 max-md:hidden">
|
||||
<div className="z-1 max-md:hidden space-y-5">
|
||||
<p className="heading1 font-medium">{text}</p>
|
||||
<div className="bg-white/30 w-full h-1 rounded-[34px]">
|
||||
<div
|
||||
className="h-1 bg-white transition-[width] rounded-[34px]"
|
||||
style={{ width: currentProgress * 100 + '%' }}
|
||||
style={{ width: currentProgress * 100 + "%" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
))}
|
||||
</div>
|
||||
{stories && stories.length > 0 && (
|
||||
<div className="md:hidden absolute space-y-6 left-2.5 right-2.5 bottom-4 z-1 w-full">
|
||||
<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]?.text}
|
||||
</p>
|
||||
@@ -156,8 +156,8 @@ export function StoriesModal({ startIndex = 0 }: { startIndex?: number }) {
|
||||
className="bg-white rounded-[34px] h-1 transition-all"
|
||||
style={
|
||||
currentIndex === index
|
||||
? { width: currentProgress * 100 + '%' }
|
||||
: { width: currentIndex > index ? '100%' : '0%' }
|
||||
? { width: currentProgress * 100 + "%" }
|
||||
: { width: currentIndex > index ? "100%" : "0%" }
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { ArticleContentFormModal } from '@/components/modals/ArticleContentFormModal';
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { useGetArticleById } from '@/queries/getArticleById';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { ArticleNewSlider } from '@/ui/ArticleNewSlider';
|
||||
import { ArticleVideoPlayer } from '@/ui/ArticleVideoPlayer';
|
||||
import { Button } from '@/ui/Button';
|
||||
import parse from 'html-react-parser';
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import ArrowMoreIcon from '../../../../public/icons/arrow_more.svg';
|
||||
import EditIcon from '../../../../public/icons/edit.svg';
|
||||
import { ReactLenis } from 'lenis/react';
|
||||
import { useLenis } from '@/hooks/useLenis';
|
||||
// import { ArticleContentFormModal } from "@/components/modals/ArticleContentFormModal";
|
||||
import { useCheckAuthQuery } from "@/queries/checkAuth";
|
||||
import { useGetArticleById } from "@/queries/getArticleById";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { ArticleNewSlider } from "@/ui/ArticleNewSlider";
|
||||
import { ArticleVideoPlayer } from "@/ui/ArticleVideoPlayer";
|
||||
import { Button } from "@/ui/Button";
|
||||
import parse from "html-react-parser";
|
||||
import Link from "next/link";
|
||||
import { Fragment, useEffect } from "react";
|
||||
import ArrowMoreIcon from "../../../../public/icons/arrow_more.svg";
|
||||
import EditIcon from "../../../../public/icons/edit.svg";
|
||||
import { ReactLenis } from "lenis/react";
|
||||
import { useLenis } from "@/hooks/useLenis";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const ArticleContentFormModal = dynamic(
|
||||
() =>
|
||||
import("@/components/modals/ArticleContentFormModal").then(
|
||||
(mod) => mod.ArticleContentFormModal
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export function ArticleSyncPage({ slug }: { slug: string }) {
|
||||
const { data: article } = useGetArticleById(slug);
|
||||
@@ -28,16 +37,16 @@ export function ArticleSyncPage({ slug }: { slug: string }) {
|
||||
if (!article) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute -translate-x-1/2 left-1/2 lg:h-[calc(100vh-40px)] lg:w-[66.25vw] w-screen lg:top-5 lg:rounded-[1.944vw] rounded-[28px] bg-[#232425] lg:m-auto overflow-y-hidden max-h-dvh h-full outline-none">
|
||||
<ReactLenis
|
||||
ref={lenis}
|
||||
className="relative h-full py-1 overflow-y-scroll overflow-x-hidden"
|
||||
className="absolute -translate-x-1/2 left-1/2 lg:h-[calc(100vh-40px)] lg:w-[66.25vw] w-screen lg:top-5 lg:rounded-[1.944vw] rounded-[28px] bg-[#232425] lg:m-auto overflow-y-hidden max-h-dvh h-full outline-none"
|
||||
>
|
||||
<div className="relative h-full py-1 overflow-x-hidden overflow-y-auto">
|
||||
<div className="relative w-full lg:h-[18.125vw] md:max-lg:h-[261px] h-[209px]">
|
||||
{auth && (
|
||||
<div className="top-3 left-4 absolute">
|
||||
<Button
|
||||
className="bg-[#37393B99] z-3 backdrop-blur-sm p-4 btnm"
|
||||
className="bg-[#37393B99] z-[3] backdrop-blur-sm p-4 btnm"
|
||||
color="secondary"
|
||||
rounded="2xl"
|
||||
onClick={() =>
|
||||
@@ -77,11 +86,11 @@ export function ArticleSyncPage({ slug }: { slug: string }) {
|
||||
<div className="lg:py-[2.778vw] lg:px-[5.208vw] py-10 md:max-lg:px-4 px-2.5 w-full md:space-y-10 space-y-8">
|
||||
{article.blocks.map((block, index) => (
|
||||
<Fragment key={index}>
|
||||
{block.type === 'Content' ? (
|
||||
{block.type === "Content" ? (
|
||||
<div className="lg:max-w-2/3 [&_p_*]:!text1 [&_h1_*]:!heading1 [&_h2_*]:!heading2">
|
||||
{parse(block.content)}
|
||||
</div>
|
||||
) : block.type === 'ButtonLink' ? (
|
||||
) : block.type === "ButtonLink" ? (
|
||||
<Link
|
||||
href={block.link}
|
||||
className="rounded-2xl bg-gradient btnm w-fit flex items-center gap-3 px-6 py-4"
|
||||
@@ -89,7 +98,7 @@ export function ArticleSyncPage({ slug }: { slug: string }) {
|
||||
{block.title}
|
||||
<ArrowMoreIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 text-white" />
|
||||
</Link>
|
||||
) : block.type === 'Quote' ? (
|
||||
) : block.type === "Quote" ? (
|
||||
<div className="lg:p-[3.333vw] md:max-lg:p-12 p-4 space-y-6 rounded-2xl bg-[radial-gradient(ellipse_at_bottom,#7A7A7A50,transparent)]">
|
||||
<div className="flex gap-4">
|
||||
<div className="aspect-square relative">
|
||||
@@ -107,9 +116,9 @@ export function ArticleSyncPage({ slug }: { slug: string }) {
|
||||
</div>
|
||||
<p className="accent font-medium">{block.text}</p>
|
||||
</div>
|
||||
) : block.type === 'Slider' ? (
|
||||
) : block.type === "Slider" ? (
|
||||
<ArticleNewSlider images={block.images.map(({ img }) => img)} />
|
||||
) : block.type === 'Video' ? (
|
||||
) : block.type === "Video" ? (
|
||||
<ArticleVideoPlayer src={block.src} />
|
||||
) : (
|
||||
<div className="relative">
|
||||
@@ -117,14 +126,14 @@ export function ArticleSyncPage({ slug }: { slug: string }) {
|
||||
className="!relative lg:rounded-[1.111vw] rounded-2xl w-full"
|
||||
src={process.env.NEXT_PUBLIC_S3_BUCKET + block.img}
|
||||
sizes="100%"
|
||||
alt={''}
|
||||
alt={""}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</ReactLenis>
|
||||
</div>
|
||||
</ReactLenis>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useCheckAuthQuery } from '@/queries/checkAuth';
|
||||
import { useGetArticlesQuery } from '@/queries/getArticles';
|
||||
import { useShowDrafted } from '@/stores/useShowDrafted';
|
||||
import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import TelegramIcon from '../../../../public/icons/tg.svg';
|
||||
import { ArticleCard } from './ArticleCard';
|
||||
import { DraftsList } from './DraftsList';
|
||||
import { useCheckAuthQuery } from "@/queries/checkAuth";
|
||||
import { useGetArticlesQuery } from "@/queries/getArticles";
|
||||
import { useShowDrafted } from "@/stores/useShowDrafted";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import TelegramIcon from "../../../../public/icons/tg.svg";
|
||||
import { ArticleCard } from "./ArticleCard";
|
||||
import { DraftsList } from "./DraftsList";
|
||||
|
||||
export function ArticlesList() {
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const { data: articles } = useGetArticlesQuery(searchParams.getAll('tags'));
|
||||
const { data: articles } = useGetArticlesQuery(searchParams.getAll("tags"));
|
||||
|
||||
const { data: auth } = useCheckAuthQuery();
|
||||
|
||||
@@ -20,8 +20,8 @@ export function ArticlesList() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6 lg:col-span-4 col-span-full lg:w-[65.972vw]">
|
||||
{auth && show && <DraftsList tags={searchParams.getAll('tags')} />}
|
||||
<div className="space-y-2 pt-2">
|
||||
{auth && show && <DraftsList tags={searchParams.getAll("tags")} />}
|
||||
<div className="pt-2 space-y-2">
|
||||
{auth && <p className="text-[#7A7A7A] text1 pl-3">Опубликованное</p>}
|
||||
<div className="lg:gap-x-[0.833vw] md:gap-x-3 gap-y-6 md:flex flex-wrap col-start-2">
|
||||
{articles &&
|
||||
@@ -35,17 +35,17 @@ export function ArticlesList() {
|
||||
<div className="bg-[#37393B99] nth-[3n+1]:m-auto nth-[3n]:w-[21.25vw] max-lg:hidden lg:rounded-[1.111vw] rounded-2xl p-[1.111vw] w-[43.611vw] h-[24.444vw] bg-[url(/img/pages/blog/tg/into_lapenko.jpg)] bg-no-repeat bg-right-bottom bg-[length:calc(582.85/628*100%)] flex flex-col justify-between">
|
||||
<div className="text1 font-medium lg:max-w-[25.069vw]">
|
||||
Мы еще не перенесли сюда все публикации, но все самое горячее
|
||||
можно прочитать в телеграмме{' '}
|
||||
можно прочитать в телеграмме{" "}
|
||||
<div className="relative inline-block rounded-full lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4 bg-[#2AABEE] align-middle">
|
||||
<TelegramIcon className="lg:w-[0.833vw] lg:h-[0.833vw] w-2 h-2 absolute left-1/2 -translate-1/2 top-1/2" />
|
||||
<TelegramIcon className="lg:w-[0.833vw] lg:h-[0.833vw] w-2 h-2 absolute left-1/2 -translate-x-1/2 -translate-y-1/2 top-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
href={'https://t.me/graffestate'}
|
||||
href={"https://t.me/graffestate"}
|
||||
className="bg-gradient lg:rounded-[1.111vw] rounded-2xl p-[1.111vw] pl-[1.667vw] flex items-center w-fit lg:gap-[0.556vw] gap-2 group relative cursor-pointer"
|
||||
>
|
||||
<div className="absolute w-full h-full top-0 left-0 group-hover:bg-black/10 rounded-2xl transition-colors" />
|
||||
<p className="btnm font-medium z-1">В телеграм</p>
|
||||
<div className="group-hover:bg-black/10 rounded-2xl absolute top-0 left-0 w-full h-full transition-colors" />
|
||||
<p className="btnm font-medium z-[1]">В телеграм</p>
|
||||
<TelegramIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-2 h-2" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Icon } from "@/ui/Icon";
|
||||
|
||||
export function ConsultationRange({
|
||||
consultations,
|
||||
@@ -17,7 +17,7 @@ export function ConsultationRange({
|
||||
|
||||
const isMouseEvent = (
|
||||
e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent
|
||||
): e is React.MouseEvent | MouseEvent => 'clientX' in e;
|
||||
): e is React.MouseEvent | MouseEvent => "clientX" in e;
|
||||
|
||||
function handleMouseDown(e: React.MouseEvent | React.TouchEvent) {
|
||||
if (!root.current) return;
|
||||
@@ -57,23 +57,23 @@ export function ConsultationRange({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, [handleMouseMove, isMouseDown]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mouseleave', handleMouseLeave);
|
||||
document.addEventListener("mouseleave", handleMouseLeave);
|
||||
return () => {
|
||||
document.removeEventListener('mouseleave', handleMouseLeave);
|
||||
document.removeEventListener("mouseleave", handleMouseLeave);
|
||||
};
|
||||
}, [handleMouseLeave, isMouseDown]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
return () => {
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
}, [handleMouseUp, isMouseDown]);
|
||||
|
||||
@@ -92,7 +92,7 @@ export function ConsultationRange({
|
||||
>
|
||||
<div
|
||||
className="absolute left-0 top-0 lg:rounded-[1.111vw] rounded-2xl h-full bg-[#37393B99] backdrop-blur-2xl flex items-center z-[2]"
|
||||
style={{ width: (consultations / 350) * 100 + '%' }}
|
||||
style={{ width: (consultations / 350) * 100 + "%" }}
|
||||
>
|
||||
<p className="btnl lg:pl-6 pl-3 font-medium select-none">
|
||||
{consultations}
|
||||
@@ -105,7 +105,7 @@ export function ConsultationRange({
|
||||
name="dots"
|
||||
svgProp={{
|
||||
className:
|
||||
'lg:w-[1.667vw] lg:h-[1.667vw] w-6 h-6 text-white select-none',
|
||||
"lg:w-[1.667vw] lg:h-[1.667vw] w-6 h-6 text-white select-none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { ItemActions } from '@/components/ItemActions';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { motion } from 'framer-motion';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import { GridItem } from 'react-grid-dnd';
|
||||
import { ItemActions } from "@/components/ItemActions";
|
||||
import { ICompany } from "@/types/ICompany";
|
||||
import { motion } from "framer-motion";
|
||||
import { forwardRef, useState } from "react";
|
||||
import { GridItem } from "react-grid-dnd";
|
||||
|
||||
export const ClientItem = forwardRef<HTMLDivElement, ICompany>(
|
||||
(company, ref) => {
|
||||
@@ -17,7 +17,7 @@ export const ClientItem = forwardRef<HTMLDivElement, ICompany>(
|
||||
return (
|
||||
<GridItem>
|
||||
<motion.div
|
||||
viewport={{ margin: '-10% 0% 0% 0%', once: true }}
|
||||
viewport={{ margin: "-10% 0% 0% 0%", once: true }}
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
onViewportEnter={handleOnViewportFeatureEnter}
|
||||
@@ -30,12 +30,12 @@ export const ClientItem = forwardRef<HTMLDivElement, ICompany>(
|
||||
</p>
|
||||
)}
|
||||
<div className="aspect-square relative flex items-center justify-center p-5">
|
||||
<div className="max-w-4/5 absolute w-full">
|
||||
<div className="max-w-[80%] absolute w-full">
|
||||
<img
|
||||
src={
|
||||
isViewportEntered
|
||||
? process.env.NEXT_PUBLIC_S3_BUCKET + company.logo!
|
||||
: ''
|
||||
: ""
|
||||
}
|
||||
className="object-cover object-center select-none pointer-events-none !relative min-w-full"
|
||||
alt={company.title}
|
||||
@@ -50,4 +50,4 @@ export const ClientItem = forwardRef<HTMLDivElement, ICompany>(
|
||||
}
|
||||
);
|
||||
|
||||
ClientItem.displayName = 'ClientItem';
|
||||
ClientItem.displayName = "ClientItem";
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { CompanyFormModal } from '@/components/modals/CompanyFormModal';
|
||||
import { OpenFormModalWrapper } from '@/hocs/OpenFormModalWrapper';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { useGetCompaniesQuery } from '@/queries/getCompanies';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { GradientButton } from '@/ui/GradientButton';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { getCompaniesCount } from '@/utils/getCompaniesCount';
|
||||
import { shuffle } from '@/utils/shuffle';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { GridContextProvider, GridDropZone, swap } from 'react-grid-dnd';
|
||||
import AddIcon from '../../../../../public/icons/add.svg';
|
||||
import RestartIcon from '../../../../../public/icons/restart.svg';
|
||||
import { ClientItem } from './ClientItem';
|
||||
import { useGetCompaniesCountQuery } from '@/queries/getCompaniesCount';
|
||||
import { CompanyFormModal } from "@/components/modals/CompanyFormModal";
|
||||
import { OpenFormModalWrapper } from "@/hocs/OpenFormModalWrapper";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useGetCompaniesQuery } from "@/queries/getCompanies";
|
||||
import { ICompany } from "@/types/ICompany";
|
||||
import { GradientButton } from "@/ui/GradientButton";
|
||||
import { Title } from "@/ui/Title";
|
||||
import { getCompaniesCount } from "@/utils/getCompaniesCount";
|
||||
import { shuffle } from "@/utils/shuffle";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { GridContextProvider, GridDropZone, swap } from "react-grid-dnd";
|
||||
import AddIcon from "../../../../../public/icons/add.svg";
|
||||
import RestartIcon from "../../../../../public/icons/restart.svg";
|
||||
import { ClientItem } from "./ClientItem";
|
||||
import { useGetCompaniesCountQuery } from "@/queries/getCompaniesCount";
|
||||
|
||||
export function Clients() {
|
||||
const { data: companies } = useGetCompaniesQuery();
|
||||
@@ -50,9 +50,9 @@ export function Clients() {
|
||||
setSize(clientRef.current!.clientWidth);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [isLg, isMd, isXs]);
|
||||
|
||||
return (
|
||||
@@ -64,7 +64,7 @@ export function Clients() {
|
||||
<Title className="mx-auto">
|
||||
<span className="text-gradient">
|
||||
{count !== undefined && getCompaniesCount(count)}
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
уже внедрили наш продукт в свою цепочку продаж
|
||||
</Title>
|
||||
<OpenFormModalWrapper
|
||||
@@ -105,7 +105,7 @@ export function Clients() {
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className="absolute flex z-1 flex-col items-center justify-center gap-3 lg:w-[calc((100vw-120px)/11)] md:max-lg:w-[calc((100vw-64px)/5)] w-[calc((100vw-36px)/3)] aspect-square"
|
||||
className="absolute flex z-[1] flex-col items-center justify-center gap-3 lg:w-[calc((100vw-120px)/11)] md:max-lg:w-[calc((100vw-64px)/5)] w-[calc((100vw-36px)/3)] aspect-square"
|
||||
style={{
|
||||
left:
|
||||
(shuffled.length % (isLg ? 11 : isMd ? 5 : 3)) * (size + 6),
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import ArrowMoreIcon from '../../../../../public/icons/arrow_more.svg';
|
||||
import YoutubeIcon from '../../../../../public/icons/youtube.svg';
|
||||
import { StoriesModal } from '@/components/modals/StoriesModal';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { useRef } from 'react';
|
||||
import { api } from "@/api";
|
||||
import ArrowMoreIcon from "../../../../../public/icons/arrow_more.svg";
|
||||
import YoutubeIcon from "../../../../../public/icons/youtube.svg";
|
||||
import { StoriesModal } from "@/components/modals/StoriesModal";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { Icon } from "@/ui/Icon";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { motion, useScroll, useTransform } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { useRef } from "react";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
export function IntegrationsDesktop() {
|
||||
@@ -16,14 +19,20 @@ export function IntegrationsDesktop() {
|
||||
|
||||
const { scrollYProgress } = useScroll({ target });
|
||||
|
||||
const x1 = useTransform(scrollYProgress, [0, 1], ['0%', '-77.5%']);
|
||||
const x1 = useTransform(scrollYProgress, [0, 1], ["0%", "-77.5%"]);
|
||||
|
||||
const x2 = useTransform(scrollYProgress, [0, 1], ['0%', '77.5%']);
|
||||
const x2 = useTransform(scrollYProgress, [0, 1], ["0%", "77.5%"]);
|
||||
|
||||
const opacity = useTransform(scrollYProgress, [0, 0.15], [1, 0]);
|
||||
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const { data: articles } = useQuery({
|
||||
queryKey: ["articles"],
|
||||
queryFn: () => api.get("articles").json<IArticle[]>(),
|
||||
select: (data) => data.filter(({ slug, drafted }) => slug && !drafted),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="max-lg:hidden relative h-[600vh] mt-[140px]" ref={target}>
|
||||
<div className="sticky top-[12vh] w-full space-y-[0.833vw]">
|
||||
@@ -73,6 +82,7 @@ export function IntegrationsDesktop() {
|
||||
title="Офис продаж Авторского квартала Машаров"
|
||||
className="w-[21.111vw] aspect-[448/354]"
|
||||
/>
|
||||
|
||||
<VideoLink
|
||||
title="Интерактивный инструмент продаж GRAFF.estate для ЖК «Will Towers»"
|
||||
src="/videos/pages/home/integrations/Will_compressed.mp4"
|
||||
@@ -91,6 +101,10 @@ export function IntegrationsDesktop() {
|
||||
linkTitle="Лучший офис продаж по версии WOW AWARDS 2023"
|
||||
className="w-[46.389vw] aspect-[812/354]"
|
||||
/>
|
||||
{/* hidden links for SEO */}
|
||||
{articles?.map(({ slug }) => (
|
||||
<Link href={`/blog/${slug}`} key={slug} className="hidden" />
|
||||
))}
|
||||
<IntegrationItem
|
||||
title="Офисы продаж Паритет девелопмент"
|
||||
mainSrc="/img/pages/home/integrations/paritet.jpg"
|
||||
@@ -127,19 +141,19 @@ export function IntegrationItem({
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`lg:rounded-[1.111vw] rounded-2xl relative overflow-hidden p-4 lg:p-[1.667vw] before:absolute before:inset-0 before:[background:linear-gradient(to_bottom_right,#00000099,transparent)] before:-z-1 flex-shrink-0 group${
|
||||
className ? ' ' + className : ''
|
||||
className={`lg:rounded-[1.111vw] rounded-2xl relative overflow-hidden p-4 lg:p-[1.667vw] before:absolute before:inset-0 before:[background:linear-gradient(to_bottom_right,#00000099,transparent)] before:-z-[1] flex-shrink-0 group${
|
||||
className ? " " + className : ""
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={mainSrc}
|
||||
className="w-full h-full left-0 top-0 absolute object-cover object-center -z-2"
|
||||
className="-z-[2] absolute top-0 left-0 object-cover object-center w-full h-full"
|
||||
alt={title}
|
||||
/>
|
||||
<p className="heading2 font-medium z-2">{title}</p>
|
||||
<p className="heading2 font-medium z-[2]">{title}</p>
|
||||
{linkSrc && linkTitle && href && (
|
||||
<div className="absolute w-[14.722vw] rounded-t-[0.833vw] left-[1.667vw] bottom-0 group-hover:translate-y-0 group-hover:opacity-100 opacity-0 translate-y-full transition-all duration-500 bg-[#37393B99] backdrop-blur p-[0.833vw] space-y-[0.556vw]">
|
||||
<div className="flex gap-[0.278vw] items-center">
|
||||
<div className="absolute w-[14.722vw] rounded-t-[0.833vw] left-[1.667vw] bottom-0 group-hover:translate-y-0 group-hover:opacity-100 opacity-0 translate-y-full transition-all duration-500 bg-[#37393B99] backdrop-blur p-[0.833vw]">
|
||||
<div className="flex gap-[0.278vw] items-center mb-[0.556vw]">
|
||||
<img src={linkSrc} className="w-[1.944vw]" alt={linkTitle} />
|
||||
<p className="text-[0.694vw]">{linkTitle}</p>
|
||||
</div>
|
||||
@@ -147,7 +161,7 @@ export function IntegrationItem({
|
||||
<Button
|
||||
type="button"
|
||||
width="full"
|
||||
className="rounded-[0.833vw] justify-center py-[0.556vw]"
|
||||
className="rounded-[0.833vw] !justify-center py-[0.556vw]"
|
||||
color="primary"
|
||||
icon={
|
||||
<ArrowMoreIcon className="w-[1.111vw] h-[1.111vw] text-white" />
|
||||
@@ -176,7 +190,7 @@ export function VideoLink({
|
||||
return (
|
||||
<div
|
||||
className={`bg-[#37393B99] lg:rounded-[1.111vw] rounded-2xl lg:p-[1.667vw] lg:pb-[2.639vw] p-4 flex-shrink-0 flex flex-col justify-between overflow-hidden relative group${
|
||||
className ? ' ' + className : ''
|
||||
className ? " " + className : ""
|
||||
}`}
|
||||
>
|
||||
<p className="heading2 font-medium">{title}</p>
|
||||
@@ -201,7 +215,7 @@ export function VideoLink({
|
||||
<YoutubeIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4" />
|
||||
}
|
||||
>
|
||||
<p className="font-medium btnl">Смотреть все</p>
|
||||
<p className="btnl font-medium">Смотреть все</p>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={link} className="lg:hidden self-end">
|
||||
@@ -212,7 +226,7 @@ export function VideoLink({
|
||||
<YoutubeIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4" />
|
||||
}
|
||||
>
|
||||
<p className="font-medium btnl">Смотреть все</p>
|
||||
<p className="btnl font-medium">Смотреть все</p>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { Title } from '@/ui/Title';
|
||||
import { IntegrationItem, VideoLink } from './IntegrationsDesktop';
|
||||
"use client";
|
||||
|
||||
import { Title } from "@/ui/Title";
|
||||
import { IntegrationItem, VideoLink } from "./IntegrationsDesktop";
|
||||
import { IArticle } from "@/types/IArticle";
|
||||
import { api } from "@/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import Link from "next/link";
|
||||
|
||||
export function IntegrationsMini() {
|
||||
const { data: articles } = useQuery({
|
||||
queryKey: ["articles"],
|
||||
queryFn: () => api.get("articles").json<IArticle[]>(),
|
||||
select: (data) => data.filter(({ slug, drafted }) => slug && !drafted),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="lg:hidden space-y-10 mt-25">
|
||||
<div className="lg:hidden space-y-10 mt-[100px]">
|
||||
<Title>Интеграция в офисы продаж</Title>
|
||||
<div className="space-y-2">
|
||||
<IntegrationItem
|
||||
@@ -24,6 +36,10 @@ export function IntegrationsMini() {
|
||||
title="Интерактивный инструмент продаж GRAFF.estate для ЖК «Will Towers»"
|
||||
className="aspect-[340/316.52]"
|
||||
/>
|
||||
{/* hidden links for SEO */}
|
||||
{articles?.map(({ slug }) => (
|
||||
<Link href={`/blog/${slug}`} key={slug} className="hidden" />
|
||||
))}
|
||||
<IntegrationItem
|
||||
title="Офисы продаж Паритет девелопмент"
|
||||
mainSrc="/img/pages/home/integrations/paritet.jpg"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useLongPress } from '@/hooks/useLongPress';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { useGetProjectsCountQuery } from '@/queries/getProjectsCount';
|
||||
import { useCityPointStore } from '@/stores/useCityPointStore';
|
||||
import { ICityProjects } from '@/types/ICityProjects';
|
||||
import { IMapProject } from '@/types/IMapProject';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
|
||||
import { useHover } from 'usehooks-ts';
|
||||
import { useLongPress } from "@/hooks/useLongPress";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useGetProjectsCountQuery } from "@/queries/getProjectsCount";
|
||||
import { useCityPointStore } from "@/stores/useCityPointStore";
|
||||
import { ICityProjects } from "@/types/ICityProjects";
|
||||
import { IMapProject } from "@/types/IMapProject";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
||||
import { useHover } from "usehooks-ts";
|
||||
|
||||
export function CityPoint({
|
||||
x,
|
||||
@@ -82,18 +82,18 @@ export function CityPoint({
|
||||
ref={pointRef}
|
||||
style={{ left: `${x}%`, top: `${y}%` }}
|
||||
className={`absolute outline-none lg:px-[1.111vw] lg:py-[0.625vw] flex lg:gap-[0.278vw] items-center transition-colors cursor-pointer select-none font-medium ${
|
||||
active ? 'text-white' : 'text-[#7A7A7A]'
|
||||
active ? "text-white" : "text-[#7A7A7A]"
|
||||
}`}
|
||||
>
|
||||
<p
|
||||
className={`heading2 font-medium${active ? ' z-2' : ''}`}
|
||||
className={`heading2 font-medium${active ? " z-[2]" : ""}`}
|
||||
ref={refTitle}
|
||||
>
|
||||
{title}
|
||||
</p>
|
||||
<p
|
||||
className={`btns lg:h-[2.083vw] font-medium h-[5.114vw]${
|
||||
active ? ' z-2' : ''
|
||||
active ? " z-[2]" : ""
|
||||
}`}
|
||||
ref={refCount}
|
||||
>
|
||||
@@ -116,7 +116,7 @@ export function CityPoint({
|
||||
},
|
||||
}}
|
||||
onAnimationComplete={() => setAnimationCompleted(true)}
|
||||
className="absolute rounded-full aspect-square z-1 [backdrop-filter:blur(3.21px)] bg-[radial-gradient(#37393B00,#37393B99)] -translate-x-1/2 left-1/2"
|
||||
className="absolute rounded-full aspect-square z-[1] [backdrop-filter:blur(3.21px)] bg-[radial-gradient(#37393B00,#37393B99)] -translate-x-1/2 left-1/2"
|
||||
/>
|
||||
{!!projects?.length &&
|
||||
circleRef.current &&
|
||||
@@ -184,7 +184,7 @@ export function LogoItem({
|
||||
top: -circleRadius,
|
||||
}}
|
||||
exit={{ opacity: 0, transition: { delay: index * 0.1 } }}
|
||||
transition={{ delay: index * 0.1 + 0.3, bounce: 'none' }}
|
||||
transition={{ delay: index * 0.1 + 0.3, bounce: "none" }}
|
||||
className="max-w-[6.264vw] w-full aspect-square absolute rounded-[1.567vw] -left-[9.049vw]"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { cities, mobileCities } from '@/consts/cities';
|
||||
import { useGetMapPointByCity } from '@/queries/getMapPointByCity';
|
||||
import { useCityPointStore } from '@/stores/useCityPointStore';
|
||||
import { useState } from 'react';
|
||||
import { CityPoint } from './CityPoint';
|
||||
import { Slider } from './Slider';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { cities, mobileCities } from "@/consts/cities";
|
||||
import { useGetMapPointByCity } from "@/queries/getMapPointByCity";
|
||||
import { useCityPointStore } from "@/stores/useCityPointStore";
|
||||
import { useState } from "react";
|
||||
import { CityPoint } from "./CityPoint";
|
||||
import { Slider } from "./Slider";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { Icon } from "@/ui/Icon";
|
||||
|
||||
export function Map() {
|
||||
const { cityPoint } = useCityPointStore();
|
||||
@@ -20,8 +20,8 @@ export function Map() {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="max-lg:overflow-x-auto overflow-y-visible h-full scrollbar-hide max-lg:aspect-[340/620] md:max-lg:-mx-4 max-md:-mx-2.5 mt-16 relative">
|
||||
<div className="lg:bg-[url(/img/pages/home/stats/map2.0.png)] bg-[url(/img/pages/home/stats/map_mobile.png)] -mt-19 bg-no-repeat bg-contain lg:aspect-[1432.4/731.98] h-full aspect-[1068.86/586.76] relative">
|
||||
<div className="max-lg:overflow-x-auto overflow-y-visible h-full [scrollbar-width:none] max-lg:aspect-[340/620] md:max-lg:-mx-4 max-md:-mx-2.5 mt-16 relative">
|
||||
<div className="lg:bg-[url(/img/pages/home/stats/map2.0.png)] bg-[url(/img/pages/home/stats/map_mobile.png)] -mt-[76px] bg-no-repeat bg-contain lg:aspect-[1432.4/731.98] h-full aspect-[1068.86/586.76] relative">
|
||||
{(isLg ? cities : mobileCities).map((point, index) => (
|
||||
<CityPoint
|
||||
key={point.title}
|
||||
@@ -44,7 +44,7 @@ export function Map() {
|
||||
<div className="w-[6.667vw] h-[6.667vw]">
|
||||
<Icon
|
||||
name="finger_print"
|
||||
svgProp={{ className: 'w-[6.667vw] h-[6.667vw]' }}
|
||||
svgProp={{ className: "w-[6.667vw] h-[6.667vw]" }}
|
||||
/>
|
||||
</div>
|
||||
<p className="caption leading-[120%] font-medium select-none">
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { videos } from '@/consts/presentation/videos';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { useMotionValueEvent, useScroll } from 'framer-motion';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Engine } from '../../../slides/Engine';
|
||||
import { Infrastructure } from '../../../slides/Infrastructure';
|
||||
import { Insolation } from '../../../slides/Insolation';
|
||||
import { IntegrationCRM } from '../../../slides/IntegrationCRM';
|
||||
import { SearchAndSelect } from '../../../slides/SearchAndSelect';
|
||||
import { ThreeDTour } from '../../../slides/ThreeDTour';
|
||||
import { VideoLayerMain } from '../../../slides/VideoLayerMain';
|
||||
import { PrimeProgressItem } from '@/ui/PrimeProgressItem';
|
||||
import { videos } from "@/consts/presentation/videos";
|
||||
import { Title } from "@/ui/Title";
|
||||
import { useMotionValueEvent, useScroll } from "framer-motion";
|
||||
import { useRef, useState } from "react";
|
||||
import { Engine } from "../../../slides/Engine";
|
||||
import { Infrastructure } from "../../../slides/Infrastructure";
|
||||
import { Insolation } from "../../../slides/Insolation";
|
||||
import { IntegrationCRM } from "../../../slides/IntegrationCRM";
|
||||
import { SearchAndSelect } from "../../../slides/SearchAndSelect";
|
||||
import { ThreeDTour } from "../../../slides/ThreeDTour";
|
||||
import { VideoLayerMain } from "../../../slides/VideoLayerMain";
|
||||
import { PrimeProgressItem } from "@/ui/PrimeProgressItem";
|
||||
|
||||
export function PresentationDesktop() {
|
||||
const target = useRef<HTMLDivElement>(null);
|
||||
@@ -23,19 +23,19 @@ export function PresentationDesktop() {
|
||||
|
||||
const [currentHovered, setCurrentHovered] = useState<number | undefined>();
|
||||
|
||||
useMotionValueEvent(scrollYProgress, 'change', (value) =>
|
||||
useMotionValueEvent(scrollYProgress, "change", (value) =>
|
||||
setSlide(Math.min(Math.trunc(value * videos.length), videos.length - 1))
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-25 mb-60 max-lg:hidden relative">
|
||||
<div className="mt-[100px] mb-[240px] max-lg:hidden relative">
|
||||
<Title className="mb-16">
|
||||
Интерактивная презентация{' '}
|
||||
<span className="text-gradient">улучшает опыт выбора недвижимости</span>{' '}
|
||||
Интерактивная презентация{" "}
|
||||
<span className="text-gradient">улучшает опыт выбора недвижимости</span>{" "}
|
||||
и увеличивает темпы продаж квартир в жилом комплексе
|
||||
</Title>
|
||||
<div className="relative h-[233.334vw]" ref={container}>
|
||||
<div className="top-30 w-full h-[38.889vw] sticky">
|
||||
<div className="top-[120px] w-full h-[38.889vw] sticky">
|
||||
<VideoLayerMain scroll={scrollYProgress} />
|
||||
<SearchAndSelect scrollProgress={scrollYProgress} page="main" />
|
||||
<ThreeDTour scrollProgress={scrollYProgress} page="main" />
|
||||
@@ -43,7 +43,7 @@ export function PresentationDesktop() {
|
||||
<Insolation scrollProgress={scrollYProgress} page="main" />
|
||||
<Engine scroll={scrollYProgress} />
|
||||
<IntegrationCRM scrollProgress={scrollYProgress} page="main" />
|
||||
<div className="flex absolute bottom-0 p-[0.556vw] rounded-[1.875vw] bg-[#37393B99] backdrop-blur-[20px] left-1/2 -translate-x-1/2 translate-y-1/2 z-50">
|
||||
<div className="flex absolute bottom-0 p-[0.556vw] rounded-[1.875vw] bg-[#37393B99] backdrop-blur-[20px] left-1/2 -translate-x-1/2 translate-y-1/2 z-[50]">
|
||||
{videos.map(({ src, anchorImg, title }, index) => (
|
||||
<PrimeProgressItem
|
||||
onClick={() => {
|
||||
@@ -69,7 +69,7 @@ export function PresentationDesktop() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div ref={target} className="h-full absolute top-0" />
|
||||
<div ref={target} className="absolute top-0 h-full" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { videos } from '@/consts/presentation/videos';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { motion, useMotionValueEvent, useScroll } from 'framer-motion';
|
||||
import { createRef, RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { Engine } from '../../../slides/Engine';
|
||||
import { Infrastructure } from '../../../slides/Infrastructure';
|
||||
import { Insolation } from '../../../slides/Insolation';
|
||||
import { IntegrationCRM } from '../../../slides/IntegrationCRM';
|
||||
import { SearchAndSelect } from '../../../slides/SearchAndSelect';
|
||||
import { ThreeDTour } from '../../../slides/ThreeDTour';
|
||||
import { videos } from "@/consts/presentation/videos";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { Title } from "@/ui/Title";
|
||||
import { motion, useMotionValueEvent, useScroll } from "framer-motion";
|
||||
import { createRef, RefObject, useEffect, useRef, useState } from "react";
|
||||
import { Engine } from "../../../slides/Engine";
|
||||
import { Infrastructure } from "../../../slides/Infrastructure";
|
||||
import { Insolation } from "../../../slides/Insolation";
|
||||
import { IntegrationCRM } from "../../../slides/IntegrationCRM";
|
||||
import { SearchAndSelect } from "../../../slides/SearchAndSelect";
|
||||
import { ThreeDTour } from "../../../slides/ThreeDTour";
|
||||
|
||||
export function PresentationMini() {
|
||||
const [slide, setSlide] = useState(0);
|
||||
@@ -30,7 +30,7 @@ export function PresentationMini() {
|
||||
videoRefs[slide].current.play();
|
||||
}, [slide, videoRefs]);
|
||||
|
||||
useMotionValueEvent(scrollYProgress, 'change', (value) =>
|
||||
useMotionValueEvent(scrollYProgress, "change", (value) =>
|
||||
setSlide(Math.ceil(value * 6))
|
||||
);
|
||||
|
||||
@@ -58,29 +58,29 @@ export function PresentationMini() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-25 lg:hidden space-y-10">
|
||||
<div className="mt-[100px] lg:hidden space-y-10">
|
||||
<div
|
||||
className="md:space-y-12 max-xs:top-0 xs:max-md:top-20 top-10 sticky space-y-5"
|
||||
ref={root}
|
||||
>
|
||||
<div ref={titleContainer}>
|
||||
<Title>
|
||||
Интерактивная презентация{' '}
|
||||
Интерактивная презентация{" "}
|
||||
<span className="text-gradient">
|
||||
улучшает опыт выбора недвижимости
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
и увеличивает темпы продаж
|
||||
<span className="max-sm:hidden">
|
||||
{' '}
|
||||
{" "}
|
||||
квартир в жилом комплексе
|
||||
</span>
|
||||
</Title>
|
||||
</div>
|
||||
<motion.div
|
||||
viewport={{ margin: '-10% 0% 0% 0%', once: true }}
|
||||
viewport={{ margin: "-10% 0% 0% 0%", once: true }}
|
||||
onViewportEnter={handleOnViewportFeatureEnter}
|
||||
ref={videoContainer}
|
||||
className="md:aspect-[721/400] aspect-[340/193] perspective-[28.117vw] w-[94.444vw] md:w-[93.88vw] mx-auto relative bg-[url(/img/pages/home/presentation/touch_screen.png)] bg-no-repeat bg-top bg-[length:70.368vw]"
|
||||
className="md:aspect-[721/400] aspect-[340/193] [perspective:28.117vw] w-[94.444vw] md:w-[93.88vw] mx-auto relative bg-[url(/img/pages/home/presentation/touch_screen.png)] bg-no-repeat bg-top bg-[length:70.368vw]"
|
||||
>
|
||||
{videos.map(({ src }, index) => (
|
||||
<video
|
||||
@@ -88,7 +88,7 @@ export function PresentationMini() {
|
||||
src={
|
||||
isViewportEntered
|
||||
? `/videos/pages/home/presentation/${src}.mp4`
|
||||
: ''
|
||||
: ""
|
||||
}
|
||||
loop
|
||||
muted
|
||||
@@ -97,8 +97,8 @@ export function PresentationMini() {
|
||||
style={{
|
||||
zIndex: videos.length - index,
|
||||
}}
|
||||
className={`absolute w-[57.977vw] object-bottom object-cover h-[28vw] origin-top top-[5vw] !rotate-x-4 left-1/2 -translate-x-1/2 transition-opacity${
|
||||
slide > index && index !== videos.length - 1 ? ' opacity-0' : ''
|
||||
className={`absolute w-[57.977vw] object-bottom object-cover h-[28vw] origin-top top-[5vw] ![rotate:x_4deg] left-1/2 -translate-x-1/2 transition-opacity${
|
||||
slide > index && index !== videos.length - 1 ? " opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import {
|
||||
queryProjectsOptions,
|
||||
useGetProjectsQuery,
|
||||
} from '@/queries/getProjects';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { getProjectsCount } from '@/utils/getProjectsCount';
|
||||
import { ProjectsSection } from '../ProjectsPage/ProjectsSection';
|
||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||
} from "@/queries/getProjects";
|
||||
import { Title } from "@/ui/Title";
|
||||
import { getProjectsCount } from "@/utils/getProjectsCount";
|
||||
import { ProjectsSection } from "../ProjectsPage/ProjectsSection";
|
||||
import { queryOptions, useQuery } from "@tanstack/react-query";
|
||||
|
||||
export function Projects() {
|
||||
// const { data: projects } = useGetProjectsQuery();
|
||||
@@ -17,11 +17,11 @@ export function Projects() {
|
||||
<div className="lg:space-y-16 sm:space-y-12 space-y-10 lg:mt-40 mt-[100px]">
|
||||
<div className="lg:flex sm:space-y-12 space-y-10">
|
||||
<Title>
|
||||
За 15 лет работы мы реализовали{' '}
|
||||
За 15 лет работы мы реализовали{" "}
|
||||
<span className="text-gradient">
|
||||
{projects && getProjectsCount(projects?.length)}{' '}
|
||||
{projects && getProjectsCount(projects?.length)}{" "}
|
||||
для застройщиков
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
в сфере интерактивных технологий
|
||||
</Title>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useGetCompaniesCountQuery } from '@/queries/getCompaniesCount';
|
||||
import { Figure } from '@/ui/Figure';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { getCompaniesCount } from '@/utils/getCompaniesCount';
|
||||
import { motion, useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { useGetCompaniesCountQuery } from "@/queries/getCompaniesCount";
|
||||
import { Figure } from "@/ui/Figure";
|
||||
import { Title } from "@/ui/Title";
|
||||
import { getCompaniesCount } from "@/utils/getCompaniesCount";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
|
||||
export function Statistics() {
|
||||
const { data: count } = useGetCompaniesCountQuery();
|
||||
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
|
||||
const inView = useInView(root, { margin: '-200px 0px 0px 0px', once: true });
|
||||
const inView = useInView(root, { margin: "-200px 0px 0px 0px", once: true });
|
||||
|
||||
return (
|
||||
<div className="lg:space-y-[4.444vw] md:max-lg:space-y-[6.25vw] space-y-[11.111vw] lg:mt-40 mt-25">
|
||||
<div className="lg:space-y-[4.444vw] md:max-lg:space-y-[6.25vw] space-y-[11.111vw] lg:mt-40 mt-[100px]">
|
||||
<Title>
|
||||
Мы разрабатываем
|
||||
<span className="text-gradient"> эффективный продукт,</span> которым
|
||||
@@ -43,7 +43,7 @@ export function Statistics() {
|
||||
opacity: +inView,
|
||||
y: inView ? 0 : 100,
|
||||
}}
|
||||
transition={{ delay: 0.3, bounce: 'none' }}
|
||||
transition={{ delay: 0.3, bounce: "none" }}
|
||||
className="lg:p-[1.667vw] p-6 flex flex-col justify-between lg:col-start-2 lg:row-span-2 lg:row-start-1 md:max-lg:col-start-1 md:max-lg:row-start-2 col-span-2 md:max-lg:aspect-[736/360] bg-[url(/img/pages/home/stats/building2.png),linear-gradient(to_bottom_right,#7A7A7A50,transparent)] bg-no-repeat bg-right-bottom bg-[length:65%,100%] lg:rounded-[1.111vw] rounded-2xl relative"
|
||||
>
|
||||
<p className="text1 lg:max-w-[35%] max-w-[70%]">
|
||||
@@ -59,7 +59,7 @@ export function Statistics() {
|
||||
opacity: +inView,
|
||||
y: inView ? 0 : 100,
|
||||
}}
|
||||
transition={{ delay: 0.6, bounce: 'none' }}
|
||||
transition={{ delay: 0.6, bounce: "none" }}
|
||||
className="lg:col-start-4 lg:row-start-1 max-md:col-span-2 lg:p-[1.667vw] md:max-lg:col-start-1 md:max-lg:row-start-3 aspect-[338/225] bg-[linear-gradient(to_top_left,#7a7a7a50,transparent)] p-6 lg:rounded-[1.111vw] rounded-2xl flex flex-col justify-between relative"
|
||||
>
|
||||
<p className="text1">
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useGetProjectsQuery } from '@/queries/getProjects';
|
||||
import { Title } from '@/ui/Title';
|
||||
import { VideoPlayer } from '@/ui/VideoPlayer';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useSwipeable } from 'react-swipeable';
|
||||
import { StreamingProject } from './StreamingProject';
|
||||
import { useGetProjectsQuery } from "@/queries/getProjects";
|
||||
import { Title } from "@/ui/Title";
|
||||
import { VideoPlayer } from "@/ui/VideoPlayer";
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { StreamingProject } from "./StreamingProject";
|
||||
|
||||
export function Streaming() {
|
||||
const { data: streamingProjects } = useGetProjectsQuery(
|
||||
'Удаленная демонстрация'
|
||||
"Удаленная демонстрация"
|
||||
);
|
||||
|
||||
const [isViewportEntered, setIsViewportEntered] = useState(false);
|
||||
@@ -43,12 +43,12 @@ export function Streaming() {
|
||||
return (
|
||||
<motion.div
|
||||
onViewportEnter={handleOnViewportFeatureEnter}
|
||||
viewport={{ margin: '-10% 0% 0% 0%', once: true }}
|
||||
viewport={{ margin: "-10% 0% 0% 0%", once: true }}
|
||||
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 select-none">
|
||||
Уникальная технология
|
||||
<span className="text-gradient"> удаленной демонстрации</span>{' '}
|
||||
<span className="text-gradient"> удаленной демонстрации</span>{" "}
|
||||
дает возможность презентовать объект покупателю из любой точки
|
||||
мира
|
||||
</Title>
|
||||
@@ -57,7 +57,7 @@ export function Streaming() {
|
||||
презентуйте объект покупателю из любой точки мира
|
||||
</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"
|
||||
className="lg:grid md:flex grid-cols-4 gap-3 px-5 md:-mx-5 md:overflow-auto [scrollbar-width:none] relative max-md:aspect-[340/344] [transform-style:preserve-3d] items-stretch"
|
||||
{...handlers}
|
||||
>
|
||||
{streamingProjects?.slice(0, 3).map((project, index, { length }) => (
|
||||
@@ -71,21 +71,21 @@ export function Streaming() {
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className={`bg-gradient-to-r from-[#FFFFFF14] to-[#FFFFFF00] [background:linear-gradient(to_right,#FFFFFF14,#FFFFFF00)] p-0.5 lg:rounded-[1.111vw] rounded-2xl flex flex-1 justify-center duration-500 items-center md:max-lg:min-w-[300px] group max-md:absolute self-stretch max-md:h-full transition-transform select-none max-md:w-[calc(100%-40px)] max-md:bg-[#0F101199] max-md:[backdrop-filter:blur(40px)] ${
|
||||
className={`bg-gradient-to-r from-[#FFFFFF14] to-[#FFFFFF00] [background:linear-gradient(to_right,#FFFFFF14,#FFFFFF00)] p-0.5 lg:rounded-[1.111vw] rounded-2xl flex flex-1 justify-center !duration-500 items-center md:min-w-[300px] group max-md:absolute self-stretch max-md:h-full transition-[scale,transform] will-change-[transform,scale] select-none max-md:w-[calc(100%-40px)] max-md:bg-[#0F101199] max-md:[backdrop-filter:blur(40px)] ${
|
||||
streamingProjects &&
|
||||
(Math.min(streamingProjects!.length + 1, 4) - 1 === current
|
||||
? 'max-md:translate-z-10'
|
||||
: 'max-md:scale-85')
|
||||
? "max-md:[transform:translateZ(40px)]"
|
||||
: "max-md:[scale:85%]")
|
||||
} ${
|
||||
streamingProjects &&
|
||||
(Math.min(streamingProjects!.length + 1, 4) - 1 ===
|
||||
(current + 1) % Math.min(streamingProjects!.length + 1, 4)
|
||||
? 'max-md:translate-x-[calc(7.5%+20px)]'
|
||||
? "max-md:translate-x-[calc(7.5%+20px)]"
|
||||
: Math.min(streamingProjects!.length + 1, 4) - 1 ===
|
||||
(current - 1 + Math.min(streamingProjects!.length + 1, 4)) %
|
||||
Math.min(streamingProjects!.length + 1, 4)
|
||||
? 'max-md:translate-x-[calc(-7.5%-20px)]'
|
||||
: '')
|
||||
? "max-md:translate-x-[calc(-7.5%-20px)]"
|
||||
: "")
|
||||
}`}
|
||||
>
|
||||
<div className="md:bg-[#0F1011] h-full w-full lg:rounded-[1.111vw] rounded-2xl flex items-center p-6">
|
||||
@@ -94,7 +94,7 @@ export function Streaming() {
|
||||
Расскажем и покажем как это работает на созвоне
|
||||
</p>
|
||||
<Link
|
||||
href={'/form'}
|
||||
href={"/form"}
|
||||
className="btnm font-medium group-hover:scale-105 duration-500 lg:px-[1.667vw] lg:py-[1.181vw] px-6 py-[17px] transition-transform lg:rounded-[0.833vw] rounded-xl bg-gradient"
|
||||
>
|
||||
Оставить заявку
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { streaming } from '@/consts/streaming';
|
||||
import { IProject } from '@/types/IProject';
|
||||
import Link from 'next/link';
|
||||
import ArrowMoreIcon from '../../../../../public/icons/arrow_more.svg';
|
||||
import { streaming } from "@/consts/streaming";
|
||||
import { IProject } from "@/types/IProject";
|
||||
import Link from "next/link";
|
||||
import ArrowMoreIcon from "../../../../../public/icons/arrow_more.svg";
|
||||
|
||||
export function StreamingProject({
|
||||
city,
|
||||
@@ -12,7 +12,7 @@ export function StreamingProject({
|
||||
index,
|
||||
current,
|
||||
count,
|
||||
}: Pick<IProject, 'city' | 'title' | 'image' | 'company'> & {
|
||||
}: Pick<IProject, "city" | "title" | "image" | "company"> & {
|
||||
href: string;
|
||||
index: number;
|
||||
current: number;
|
||||
@@ -20,14 +20,16 @@ export function StreamingProject({
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`lg:aspect-[344/396] aspect-[300/344] flex-1 md:max-lg:min-w-[300px] lg:rounded-[1.111vw] rounded-2xl lg:p-[1.111vw] p-4 flex duration-500 relative overflow-hidden transition-transform group max-md:absolute max-md:w-[calc(100%-40px)] select-none h-full max-md:${
|
||||
index === current ? 'translate-z-10' : 'scale-85'
|
||||
} max-md:${
|
||||
className={`lg:aspect-[344/396] aspect-[300/344] flex-1 md:max-lg:min-w-[300px] transition-[scale,transform] will-change-[transform,scale] lg:rounded-[1.111vw] rounded-2xl lg:p-[1.111vw] p-4 flex duration-500 relative overflow-hidden group max-md:absolute max-md:w-[calc(100%-40px)] select-none h-full ${
|
||||
index === current
|
||||
? "max-md:[transform:translateZ(40px)]"
|
||||
: "max-md:[scale:85%]"
|
||||
} ${
|
||||
index === (current + 1) % count
|
||||
? 'translate-x-[calc(7.5%+20px)]'
|
||||
? "max-md:[transform:translateX(calc(7.5%+20px))]"
|
||||
: index === (current - 1 + count) % count
|
||||
? 'translate-x-[calc(-7.5%-20px)]'
|
||||
: ''
|
||||
? "max-md:[transform:translateX(calc(-7.5%-20px))]"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
@@ -56,18 +58,18 @@ export function StreamingProject({
|
||||
<div className="bottom-4 right-4 lg:hidden absolute">
|
||||
<Link
|
||||
className="bg-gradient rounded-xl btns flex items-center gap-2 px-3 py-2 font-medium"
|
||||
href={streaming.find((s) => s.title === title)?.url ?? '/'}
|
||||
href={streaming.find((s) => s.title === title)?.url ?? "/"}
|
||||
>
|
||||
Смотреть
|
||||
<ArrowMoreIcon className="w-4 h-4 text-white" />
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
href={streaming.find((s) => s.title === title)?.url ?? '/'}
|
||||
href={streaming.find((s) => s.title === title)?.url ?? "/"}
|
||||
className="max-lg:hidden lg:group-hover:opacity-100 opacity-0 transition-opacity duration-500 absolute w-full h-full left-0 bottom-0 md:max-lg:rounded-2xl rounded-xl font-medium [backdrop-filter:blur(3px)] content-center text-center z-0"
|
||||
>
|
||||
<p className="btnl flex justify-center gap-2">
|
||||
Начать демонстрацию{' '}
|
||||
Начать демонстрацию{" "}
|
||||
<ArrowMoreIcon className="text-white w-[1.389vw] h-[1.389vw]" />
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function ThreeDReelsCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,14 +10,14 @@ export function ThreeDReelsCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 10),
|
||||
bottom: slide > 10 ? '0vw' : undefined,
|
||||
bottom: slide > 10 ? "0vw" : undefined,
|
||||
}}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] w-[13.611vw] h-[36.447vh] flex flex-col justify-between absolute bg-[#37393B99] right-[27.847vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] w-[13.611vw] h-[36.447vh] flex flex-col justify-between absolute bg-[#37393B99] right-[27.847vw] cursor-pointer"
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<PrimeModal
|
||||
categoryTitle="Рекламные материалы"
|
||||
packages={['Стандарт']}
|
||||
packages={["Стандарт"]}
|
||||
src="/videos/pages/prime/3d_reels.mp4"
|
||||
title="3D-рилс"
|
||||
text="3D-рилс — это быстрый путь в Instagram, TikTok и остальные соцсети. Наглядная мини-презентация ЖК в динамике, которую можно оперативно выпустить в ленту и зацепить потенциальных клиентов."
|
||||
@@ -27,12 +27,12 @@ export function ThreeDReelsCard({ slide }: { slide: number }) {
|
||||
>
|
||||
<p className="btns font-medium px-[0.833vw] py-[0.486vw] rounded-[1.181vw] bg-[#37393B99] backdrop-blur-xs self-end">
|
||||
{slide === 11
|
||||
? '1 ролик'
|
||||
? "1 ролик"
|
||||
: slide === 12
|
||||
? '3 ролика'
|
||||
? "3 ролика"
|
||||
: slide === 13
|
||||
? '6 роликов'
|
||||
: '12 роликов'}
|
||||
? "6 роликов"
|
||||
: "12 роликов"}
|
||||
</p>
|
||||
<img
|
||||
src="/img/pages/prime/phone.png"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function AnaliticsCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function AnaliticsCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 11),
|
||||
bottom: slide > 11 ? '21.842vh' : '93.684vh',
|
||||
bottom: slide > 11 ? "21.842vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,12 +18,12 @@ export function AnaliticsCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Аналитика поведения пользователя во время презентации"
|
||||
text="Речевая аналитика позволяет совершенствовать отдел продаж, выявляя «болевые» точки общения, улучшать скрипты и грамотно обучать персонал, что приводит к росту конверсии."
|
||||
packages={['Премиум', 'Бизнес']}
|
||||
packages={["Премиум", "Бизнес"]}
|
||||
src="/img/pages/prime/analyse.png"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] -translate-y-[1.389vw] rounded-[1.389vw] absolute bg-[#37393B99] right-[1.667vw] w-[15.486vw] h-[28.816vh] flex flex-col justify-between"
|
||||
className="p-[1.389vw] -translate-y-[1.389vw] rounded-[1.389vw] absolute bg-[#37393B99] right-[1.667vw] w-[15.486vw] h-[28.816vh] flex flex-col justify-between cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/analyse.png"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function ArchVisCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function ArchVisCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 11),
|
||||
bottom: slide > 11 ? '26.316vh' : '93.684vh',
|
||||
bottom: slide > 11 ? "26.316vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,19 +18,19 @@ export function ArchVisCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Рекламные материалы"
|
||||
title="Архитектурная визуализация"
|
||||
text="Архитектурная визуализация — это «классика» рекламы в недвижимости. Красивые рендеры перекликаются с интерактивной презентацией, формируя единый облик проекта и повышая его узнаваемость."
|
||||
packages={['Премиум', 'Бизнес']}
|
||||
packages={["Премиум", "Бизнес"]}
|
||||
src="/img/pages/prime/seasons.png"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="w-[15.486vw] h-[29.211vh] -translate-y-[1.389vw] p-[1.389vw] rounded-[1.389vw] bg-[#37393B99] flex flex-col justify-between absolute bg-[length:9.979vw] bg-[url(/img/pages/prime/architecture.png)] bg-no-repeat bg-center"
|
||||
className="w-[15.486vw] h-[29.211vh] -translate-y-[1.389vw] p-[1.389vw] rounded-[1.389vw] bg-[#37393B99] flex flex-col justify-between absolute bg-[length:9.979vw] bg-[url(/img/pages/prime/architecture.png)] bg-no-repeat bg-center cursor-pointer"
|
||||
>
|
||||
<p className="px-[0.833vw] py-[0.486vw] rounded-[1.181vw] btns font-medium bg-[#37393B99] self-end">
|
||||
{slide === 12
|
||||
? '3 рендера'
|
||||
? "3 рендера"
|
||||
: slide === 13
|
||||
? '6 рендеров'
|
||||
: '18 рендеров'}
|
||||
? "6 рендеров"
|
||||
: "18 рендеров"}
|
||||
</p>
|
||||
<p className="btns font-medium">Архитектурная визуализация</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function AvatarCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function AvatarCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 13),
|
||||
bottom: slide > 13 ? '56.711vh' : '93.684vh',
|
||||
bottom: slide > 13 ? "56.711vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,12 +18,12 @@ export function AvatarCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Аватар клиента"
|
||||
text="Аватар клиента — ещё один шаг к полной цифровой симуляции. «Войдя» в роль внутри 3D-презентации, покупатель ощущает реальное присутствие и меньше сомневается, подходит ли ему такой формат пространства."
|
||||
packages={['Комфорт+']}
|
||||
packages={["Комфорт+"]}
|
||||
src="/img/pages/prime/avatar.png"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-between items-center absolute bg-[#37393B99] w-[8.75vw] h-[20.132vh] right-[17.778vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-between items-center absolute bg-[#37393B99] w-[8.75vw] h-[20.132vh] right-[17.778vw] cursor-pointer"
|
||||
>
|
||||
<img src="/img/pages/prime/avatar.png" alt="" />
|
||||
<p className="btns font-medium">Аватар клиента</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function EngineCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function EngineCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 11),
|
||||
bottom: slide > 11 ? '21.842vh' : '93.684vh',
|
||||
bottom: slide > 11 ? "21.842vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,12 +18,12 @@ export function EngineCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Модуль инженерных систем"
|
||||
text="Модуль инженерных систем говорит о серьёзности подхода застройщика: если он готов показать инженерную «начинку», значит, скрывать нечего. Это особенно важно для клиентов, которые ценят технологичность."
|
||||
packages={['Комфорт+']}
|
||||
packages={["Комфорт+"]}
|
||||
src="/videos/pages/home/presentation/5_engine.mp4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="w-[9.236vw] h-[33.289vh] -translate-y-[1.389vw] p-[1.389vw] rounded-[1.389vw] absolute bg-[#37393B99] right-[17.778vw] flex flex-col justify-between"
|
||||
className="w-[9.236vw] h-[33.289vh] -translate-y-[1.389vw] p-[1.389vw] rounded-[1.389vw] absolute bg-[#37393B99] right-[17.778vw] flex flex-col justify-between cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/ruble.png"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { categoryDescription } from '@/consts/categories';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
import { categoryDescription } from "@/consts/categories";
|
||||
|
||||
export function EnvironmentCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -12,22 +12,22 @@ export function EnvironmentCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 10),
|
||||
bottom: slide > 10 ? '0vw' : undefined,
|
||||
bottom: slide > 10 ? "0vw" : undefined,
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<PrimeModal
|
||||
src={'/videos/pages/home/presentation/3_infrastructure.mp4'}
|
||||
packages={['Премиум', 'Бизнес', 'Комфорт+', 'Стандарт']}
|
||||
src={"/videos/pages/home/presentation/3_infrastructure.mp4"}
|
||||
packages={["Премиум", "Бизнес", "Комфорт+", "Стандарт"]}
|
||||
categoryTitle="Детальная проработка окружения"
|
||||
title={'Детальная проработка ЖК и ближайшего благоустойства'}
|
||||
title={"Детальная проработка ЖК и ближайшего благоустойства"}
|
||||
text={
|
||||
categoryDescription['Детальная проработка окружения'][0].text1
|
||||
categoryDescription["Детальная проработка окружения"][0].text1
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] absolute bg-[url(/img/pages/prime/summer.jpg)] bg-cover bg-center flex items-end w-[23.681vw] h-[41.053vh] left-[17.708vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] absolute bg-[url(/img/pages/prime/summer.jpg)] bg-cover bg-center flex items-end w-[23.681vw] h-[41.053vh] left-[17.708vw] cursor-pointer"
|
||||
>
|
||||
<p className="btns max-w-2/3 font-medium">
|
||||
Детальная проработка ЖК и ближайшего благоустойства
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function EquipmentCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function EquipmentCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 10),
|
||||
bottom: slide > 10 ? '0vw' : undefined,
|
||||
bottom: slide > 10 ? "0vw" : undefined,
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,53 +18,53 @@ export function EquipmentCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Оборудование"
|
||||
title={
|
||||
slide === 11
|
||||
? 'Настенная панель'
|
||||
? "Настенная панель"
|
||||
: slide === 12
|
||||
? 'Брендированный стол 800 нит'
|
||||
: 'Брендированный стол 2500 нит'
|
||||
? "Брендированный стол 800 нит"
|
||||
: "Брендированный стол 2500 нит"
|
||||
}
|
||||
text={
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
}
|
||||
packages={
|
||||
slide === 11
|
||||
? ['Стандарт']
|
||||
? ["Стандарт"]
|
||||
: slide === 12
|
||||
? ['Комфорт+', 'Стандарт']
|
||||
? ["Комфорт+", "Стандарт"]
|
||||
: slide === 13
|
||||
? ['Бизнес', 'Комфорт+', 'Стандарт']
|
||||
: ['Премиум', 'Бизнес', 'Комфорт+', 'Стандарт']
|
||||
? ["Бизнес", "Комфорт+", "Стандарт"]
|
||||
: ["Премиум", "Бизнес", "Комфорт+", "Стандарт"]
|
||||
}
|
||||
src={
|
||||
slide === 11
|
||||
? '/img/pages/prime/wallPanel.png'
|
||||
: '/img/pages/prime/brandTablet800.png'
|
||||
? "/img/pages/prime/wallPanel.png"
|
||||
: "/img/pages/prime/brandTablet800.png"
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="right-[11.458vw] p-[1.389vw] -translate-y-[1.389vw] rounded-[1.389vw] w-[15.556vw] h-[20.263vh] bg-[#37393B99] before:z-1 before:absolute before:inset-0 before:bg-gradient-to-t before:from-[#37393B99] overflow-hidden backdrop-blur-xs bg-cover bg-[position:calc(8/225*100%)_calc(15/225*100%)] flex items-end absolute"
|
||||
className="right-[11.458vw] p-[1.389vw] -translate-y-[1.389vw] rounded-[1.389vw] w-[15.556vw] h-[20.263vh] bg-[#37393B99] before:z-[1] before:absolute before:inset-0 before:bg-gradient-to-t before:from-[#37393B99] overflow-hidden backdrop-blur-xs bg-cover bg-[position:calc(8/225*100%)_calc(15/225*100%)] flex items-end absolute cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/wallPanel.png"
|
||||
className={`absolute w-[15.625vw] transition-opacity bottom-0 left-1/2 -translate-x-1/2 object-cover object-center duration-300 ${
|
||||
slide === 11 ? 'opacity-100' : 'opacity-0'
|
||||
slide === 11 ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
alt="wall panel"
|
||||
/>
|
||||
<img
|
||||
src="/img/pages/prime/brandTablet800.png"
|
||||
className={`absolute w-[15.625vw] transition-opacity -bottom-1/3 left-1/2 -translate-x-1/2 object-cover object-center duration-300 ${
|
||||
slide > 11 ? 'opacity-100' : 'opacity-0'
|
||||
slide > 11 ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
alt="brand tablet"
|
||||
/>
|
||||
<p className="btns font-medium z-1">
|
||||
<p className="btns font-medium z-[1]">
|
||||
{slide === 11
|
||||
? 'Настенная панель'
|
||||
? "Настенная панель"
|
||||
: slide === 12
|
||||
? 'Брендированный интерактивный стол 800 нит'
|
||||
: 'Брендированный интерактивный стол 2500 нит'}
|
||||
? "Брендированный интерактивный стол 800 нит"
|
||||
: "Брендированный интерактивный стол 2500 нит"}
|
||||
</p>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function ExcursionVRCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -13,8 +13,8 @@ export function ExcursionVRCard({ slide }: { slide: number }) {
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<PrimeModal
|
||||
src={'/img/pages/prime/vrvr.png'}
|
||||
packages={['Премиум', 'Бизнес']}
|
||||
src={"/img/pages/prime/vrvr.png"}
|
||||
packages={["Премиум", "Бизнес"]}
|
||||
title="Экскурсия в VR"
|
||||
text="Создается по референсам от заказчика в 1-3 вариантах. Квартира «оживает» мебелью и декором, это эмоционально «цепляет» потенциального покупателя."
|
||||
categoryTitle="Опции"
|
||||
@@ -23,9 +23,9 @@ export function ExcursionVRCard({ slide }: { slide: number }) {
|
||||
}
|
||||
animate={{
|
||||
opacity: +(slide > 12),
|
||||
bottom: slide > 12 ? '42.632vh' : '93.684vh',
|
||||
bottom: slide > 12 ? "42.632vh" : "93.684vh",
|
||||
}}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-between absolute bg-[#37393B99] w-[10.347vw] h-[16.842vh] left-[17.708vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-between absolute bg-[#37393B99] w-[10.347vw] h-[16.842vh] left-[17.708vw] cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/ruble.png"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function ExtraInterestPointsCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function ExtraInterestPointsCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 12),
|
||||
bottom: slide > 12 ? '16.597vw' : '27.362vw',
|
||||
bottom: slide > 12 ? "16.597vw" : "27.362vw",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,19 +18,19 @@ export function ExtraInterestPointsCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Дополнительные точки интереса"
|
||||
text="Покупатель имеет возможность оценить всю инфраструктуру ЖК. Это помогает закрывать сделку, особенно если комплекс богат опциями для досуга."
|
||||
packages={['Премиум']}
|
||||
packages={["Премиум"]}
|
||||
src="/videos/pages/prime/interes.mp4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] -translate-y-[1.389vw] rounded-[1.389vw] flex flex-col justify-end absolute w-[15.556vw] h-[25vh] left-[42.222vw] bg-[#37393B99] bg-[url(/img/pages/prime/wheel.png)] bg-contain bg-no-repeat bg-right-bottom before:absolute before:inset-0 before:bg-gradient-to-t before:from-[#37393B99] overflow-hidden"
|
||||
className="p-[1.389vw] -translate-y-[1.389vw] rounded-[1.389vw] flex flex-col justify-end absolute w-[15.556vw] h-[25vh] left-[42.222vw] bg-[#37393B99] bg-[url(/img/pages/prime/wheel.png)] bg-contain bg-no-repeat bg-right-bottom before:absolute before:inset-0 before:bg-gradient-to-t before:from-[#37393B99] overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/ruble.png"
|
||||
className="absolute w-[0.833vw] top-[1.389vw]"
|
||||
alt=""
|
||||
/>
|
||||
<p className="btns font-medium max-w-1/2 z-1">
|
||||
<p className="btns font-medium max-w-1/2 z-[1]">
|
||||
Дополнительные точки интереса
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function FinanceToolCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function FinanceToolCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 12),
|
||||
bottom: slide > 12 ? '42.632vh' : '93.684vh',
|
||||
bottom: slide > 12 ? "42.632vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,15 +18,15 @@ export function FinanceToolCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Финансовые инсументы (ипотека, рассрочка)"
|
||||
text="Финансовые инструменты превращают продажу в единый процесс: все расчёты на месте, клиент не уходит «подумать» и не ищет сторонние калькуляторы, а значит, высок шанс, что сделка состоится быстрее."
|
||||
packages={['Премиум', 'Бизнес']}
|
||||
packages={["Премиум", "Бизнес"]}
|
||||
src="/videos/pages/prime/financial.mp4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-end absolute bg-[#37393B99] w-[12.5vw] h-[23.684vh] left-[28.889vw] bg-[url(/img/pages/prime/finance.png)] bg-[length:12.083vw] overflow-hidden bg-no-repeat bg-[position:1.944vw_1.944vw] before:absolute before:w-[12.083vw] before:h-[12.083vw] before:-bottom-[22px] before:-right-[22px] before:bg-[linear-gradient(#27282A00,#27282A)]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-end absolute bg-[#37393B99] w-[12.5vw] h-[23.684vh] left-[28.889vw] bg-[url(/img/pages/prime/finance.png)] bg-[length:12.083vw] overflow-hidden bg-no-repeat bg-[position:1.944vw_1.944vw] before:absolute before:w-[12.083vw] before:h-[12.083vw] before:-bottom-[22px] before:-right-[22px] before:bg-[linear-gradient(#27282A00,#27282A)] cursor-pointer"
|
||||
>
|
||||
<p className="btns font-medium z-1">
|
||||
Финансовые инсументы{' '}
|
||||
<p className="btns font-medium z-[1]">
|
||||
Финансовые инсументы{" "}
|
||||
<span className="text-[#7A7A7A]">(ипотека, рассрочка)</span>
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { primeVideos } from '@/consts/presentation/videos';
|
||||
import { primeVideos } from "@/consts/presentation/videos";
|
||||
import {
|
||||
MotionValue,
|
||||
motion,
|
||||
useMotionValueEvent,
|
||||
useTransform,
|
||||
} from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
} from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
export function FolderAnimation({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const [slide, setSlide] = useState(0);
|
||||
|
||||
useMotionValueEvent(scroll, 'change', (value) =>
|
||||
useMotionValueEvent(scroll, "change", (value) =>
|
||||
setSlide(
|
||||
Math.min(
|
||||
Math.trunc(value * (primeVideos.length + 9)),
|
||||
@@ -22,7 +22,7 @@ export function FolderAnimation({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const y = useTransform(
|
||||
scroll,
|
||||
[6 / (primeVideos.length + 7), 7 / (primeVideos.length + 7)],
|
||||
['49.444vw', '0vw']
|
||||
["49.444vw", "0vw"]
|
||||
);
|
||||
|
||||
const scale = useTransform(
|
||||
@@ -34,13 +34,13 @@ export function FolderAnimation({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const bottomBorder = useTransform(
|
||||
scroll,
|
||||
[9 / (primeVideos.length + 7), 10 / (primeVideos.length + 7)],
|
||||
['17.014vw', '1.389vw']
|
||||
["17.014vw", "1.389vw"]
|
||||
);
|
||||
|
||||
const bottomFolder = useTransform(
|
||||
scroll,
|
||||
[9 / (primeVideos.length + 7), 10 / (primeVideos.length + 7)],
|
||||
['19.931vw', '4.375vw']
|
||||
["19.931vw", "4.375vw"]
|
||||
);
|
||||
|
||||
const opacity = useTransform(
|
||||
@@ -55,42 +55,42 @@ export function FolderAnimation({ scroll }: { scroll: MotionValue<number> }) {
|
||||
animate={
|
||||
slide > 7
|
||||
? {
|
||||
bottom: slide > 9 ? '2.986vw' : '19.931vw',
|
||||
width: '13.958vw',
|
||||
height: '12.014vw',
|
||||
bottom: slide > 9 ? "2.986vw" : "19.931vw",
|
||||
width: "13.958vw",
|
||||
height: "12.014vw",
|
||||
}
|
||||
: {
|
||||
bottom: '12.639vw',
|
||||
width: '15.417vw',
|
||||
height: '13.194vw',
|
||||
bottom: "12.639vw",
|
||||
width: "15.417vw",
|
||||
height: "13.194vw",
|
||||
}
|
||||
}
|
||||
style={{ y, scale, bottom: bottomFolder }}
|
||||
transition={{ bounce: 'none' }}
|
||||
style={{ y, scale, bottom: bottomFolder, x: "-50%" }}
|
||||
transition={{ bounce: "none" }}
|
||||
src="/icons/folderBack.svg"
|
||||
className="left-1/2 absolute -translate-x-1/2"
|
||||
className="left-1/2 absolute"
|
||||
/>
|
||||
<motion.img
|
||||
animate={
|
||||
slide > 7
|
||||
? {
|
||||
bottom: slide > 9 ? '2.986vw' : '19.931vw',
|
||||
width: '15.833vw',
|
||||
height: '9.792vw',
|
||||
bottom: slide > 9 ? "2.986vw" : "19.931vw",
|
||||
width: "15.833vw",
|
||||
height: "9.792vw",
|
||||
}
|
||||
: { width: '17.361vw', height: '10.764vw', bottom: '12.639vw' }
|
||||
: { width: "17.361vw", height: "10.764vw", bottom: "12.639vw" }
|
||||
}
|
||||
style={{ y, scale, bottom: bottomFolder }}
|
||||
transition={{ bounce: 'none' }}
|
||||
style={{ y, scale, bottom: bottomFolder, x: "-50%" }}
|
||||
transition={{ bounce: "none" }}
|
||||
src="/icons/folderFront.svg"
|
||||
className="left-1/2 absolute z-12 -translate-x-1/2"
|
||||
className="left-1/2 absolute z-[12]"
|
||||
/>
|
||||
<motion.div
|
||||
style={{
|
||||
bottom: bottomBorder,
|
||||
opacity,
|
||||
}}
|
||||
transition={{ bounce: 'none' }}
|
||||
transition={{ bounce: "none" }}
|
||||
className="rounded-[1.389vw] border border-[#37393B] aspect-square w-[15.972vw] absolute left-1/2 -translate-x-1/2 p-[1.111vw] flex items-end"
|
||||
>
|
||||
<p className="btns font-medium">Базовый функционал</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function InteractiveWindowCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function InteractiveWindowCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 13),
|
||||
bottom: slide > 13 ? '57.105vh' : '93.684vh',
|
||||
bottom: slide > 13 ? "57.105vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,19 +18,19 @@ export function InteractiveWindowCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Интерактивное окно"
|
||||
text="Интерактивное окно выделяется среди стандартных экспозиций: клиенты подходят, «заглядывают» в виртуальный вид, и это запоминается надолго, усиливая эмоциональную связь с проектом."
|
||||
packages={['Премиум']}
|
||||
packages={["Премиум"]}
|
||||
src="/img/pages/prime/interactiveWindow.png"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-end absolute bg-[#37393B99] w-[15.486vw] h-[36.316vh]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-end absolute bg-[#37393B99] w-[15.486vw] h-[36.316vh] cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/interactiveWindow.png"
|
||||
className="absolute left-[2.014vw] bottom-0 h-full"
|
||||
alt=""
|
||||
/>
|
||||
<p className="btns font-medium z-1">Интерактивное окно</p>
|
||||
<p className="btns font-medium z-[1]">Интерактивное окно</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import SwitchIcon from '../../../../../public/icons/switch.svg';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import SwitchIcon from "../../../../../public/icons/switch.svg";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
|
||||
export function InterierConfiguratorCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -11,20 +11,20 @@ export function InterierConfiguratorCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 11),
|
||||
bottom: slide > 11 ? '38.026vh' : '93.684vh',
|
||||
bottom: slide > 11 ? "38.026vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<PrimeModal
|
||||
categoryTitle="Опции"
|
||||
packages={['Премиум', 'Бизнес']}
|
||||
packages={["Премиум", "Бизнес"]}
|
||||
text="Конфигуратор интерьера убирает лишние сомнения: клиент видит сразу несколько вариантов отделки и выбирает тот, что ближе к его вкусам — что часто ускоряет заключение договора."
|
||||
title="Конфигуратор интерьера"
|
||||
src="/img/pages/prime/configuratorInterior.png"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] absolute bg-[#37393B99] w-[13.542vw] h-[17.105vh] flex flex-col justify-between backdrop-blur-[47.6px] right-[27.847vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] absolute bg-[#37393B99] w-[13.542vw] h-[17.105vh] flex flex-col justify-between backdrop-blur-[47.6px] right-[27.847vw] cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/prime/ruble.png"
|
||||
@@ -37,7 +37,7 @@ export function InterierConfiguratorCard({ slide }: { slide: number }) {
|
||||
alt=""
|
||||
className="rounded-full w-[4.444vw] aspect-square"
|
||||
/>
|
||||
<div className="bg-gradient rounded-full p-[0.538vw] absolute z-1 left-1/2 top-1/2 -translate-1/2">
|
||||
<div className="bg-gradient rounded-full p-[0.538vw] absolute z-[1] left-1/2 top-1/2 -translate-1/2">
|
||||
<SwitchIcon className="text-white w-[1.076vw] h-[1.076vw]" />
|
||||
</div>
|
||||
<img
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Title } from '@/ui/Title';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Title } from "@/ui/Title";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
export function PackageTitle({ slide }: { slide: number }) {
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: +(slide > 10) }}
|
||||
className="absolute space-y-[2.639vw] left-1/2 -translate-x-1/2 overflow-hidden top-[calc(100px-1.389vw)]"
|
||||
className="absolute space-y-[2.639vw] left-1/2 !-translate-x-1/2 overflow-hidden top-[calc(100px-1.389vw)]"
|
||||
>
|
||||
<Title headerLevel={1} className="text-center">
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 11 && (
|
||||
<motion.span
|
||||
initial={{ y: '100%', opacity: 0 }}
|
||||
initial={{ y: "100%", opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: '-100%', opacity: 0 }}
|
||||
exit={{ y: "-100%", opacity: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
>
|
||||
стандарт
|
||||
@@ -24,9 +24,9 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
{slide === 12 && (
|
||||
<motion.span
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ y: '100%', opacity: 0 }}
|
||||
initial={{ y: "100%", opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: '-100%', opacity: 0 }}
|
||||
exit={{ y: "-100%", opacity: 0 }}
|
||||
>
|
||||
комфорт+
|
||||
</motion.span>
|
||||
@@ -36,9 +36,9 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
{slide === 13 && (
|
||||
<motion.span
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ y: '100%', opacity: 0 }}
|
||||
initial={{ y: "100%", opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: '-100%', opacity: 0 }}
|
||||
exit={{ y: "-100%", opacity: 0 }}
|
||||
>
|
||||
бизнес
|
||||
</motion.span>
|
||||
@@ -48,9 +48,9 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
{slide === 14 && (
|
||||
<motion.span
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ y: '100%', opacity: 0 }}
|
||||
initial={{ y: "100%", opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: '-100%', opacity: 0 }}
|
||||
exit={{ y: "-100%", opacity: 0 }}
|
||||
>
|
||||
премиум
|
||||
</motion.span>
|
||||
@@ -60,13 +60,13 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<motion.div className="flex gap-[0.556vw] justify-center">
|
||||
<p
|
||||
className={`px-[0.833vw] py-[0.764vw] rounded-[1.181vw] btnm font-medium relative w-[8.056vw] h-[2.5vw] ${
|
||||
slide > 12 ? 'bg-gradient' : 'bg-[#B5F54E] text-black'
|
||||
slide > 12 ? "bg-gradient" : "bg-[#B5F54E] text-black"
|
||||
}`}
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 11 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -79,7 +79,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 12 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
@@ -92,7 +92,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 13 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
@@ -105,7 +105,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 14 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
@@ -120,7 +120,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 11 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -133,7 +133,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 12 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
@@ -146,7 +146,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 13 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
@@ -159,7 +159,7 @@ export function PackageTitle({ slide }: { slide: number }) {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{slide === 14 && (
|
||||
<motion.span
|
||||
className="absolute left-1/2 top-1/2 -translate-1/2"
|
||||
className="absolute left-1/2 top-1/2 !-translate-x-1/2 !-translate-y-1/2"
|
||||
transition={{ duration: 0.4 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { primeVideos } from '@/consts/presentation/videos';
|
||||
import { primeProgressItemsTranslates } from '@/consts/primeProgressItemsTranslates';
|
||||
import { PrimeProgressItem } from '@/ui/PrimeProgressItem';
|
||||
import { primeVideos } from "@/consts/presentation/videos";
|
||||
import { primeProgressItemsTranslates } from "@/consts/primeProgressItemsTranslates";
|
||||
import { PrimeProgressItem } from "@/ui/PrimeProgressItem";
|
||||
import {
|
||||
motion,
|
||||
MotionValue,
|
||||
useMotionValueEvent,
|
||||
useTransform,
|
||||
} from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
} from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
export function PrimeProgress({
|
||||
scroll,
|
||||
@@ -18,7 +18,7 @@ export function PrimeProgress({
|
||||
}) {
|
||||
const [slide, setSlide] = useState(0);
|
||||
|
||||
useMotionValueEvent(scroll, 'change', (value) =>
|
||||
useMotionValueEvent(scroll, "change", (value) =>
|
||||
setSlide(
|
||||
Math.min(
|
||||
Math.trunc(value * (primeVideos.length + 9)),
|
||||
@@ -36,7 +36,7 @@ export function PrimeProgress({
|
||||
9 / (primeVideos.length + 7),
|
||||
10 / (primeVideos.length + 7),
|
||||
],
|
||||
['0vw', '31.389vw', '31.389vw', '28.794vw', '13.583vw']
|
||||
["0vw", "31.389vw", "31.389vw", "28.794vw", "13.583vw"]
|
||||
);
|
||||
|
||||
const scale = useTransform(
|
||||
@@ -49,11 +49,13 @@ export function PrimeProgress({
|
||||
<motion.div
|
||||
style={{
|
||||
bottom,
|
||||
background: slide > 7 ? 'transparent' : '#37393B99',
|
||||
background: slide > 7 ? "transparent" : "#37393B99",
|
||||
scale,
|
||||
x: "-50%",
|
||||
y: "-50%",
|
||||
}}
|
||||
transition={{ bounce: false }}
|
||||
className="flex absolute p-[0.556vw] z-10 rounded-[1.875vw] bg-[#37393B99] left-1/2 -translate-1/2 transition-colors"
|
||||
className="flex absolute p-[0.556vw] z-10 rounded-[1.875vw] bg-[#37393B99] left-1/2 transition-colors"
|
||||
>
|
||||
{primeVideos.map(({ src, anchorImg, title }, index) => (
|
||||
<PrimeProgressItem
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function ScenarioCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -10,7 +10,7 @@ export function ScenarioCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 13),
|
||||
bottom: slide > 13 ? '78.421vh' : '93.684vh',
|
||||
bottom: slide > 13 ? "78.421vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -18,12 +18,12 @@ export function ScenarioCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Опции"
|
||||
title="Сценарии проживания"
|
||||
text="Сценарии проживания — это уже не просто «стены и планировка», а история жизни в новом месте. Такой приём отлично работает на эмоциональном уровне и помогает клиенту представить себя внутри проекта."
|
||||
packages={['Премиум']}
|
||||
packages={["Премиум"]}
|
||||
src="/img/pages/prime/scenarioModal.jpg"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex items-end gap-4 absolute bg-[#37393B99] w-[15.764vw] h-[15.395vh] right-[1.667vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex items-end gap-4 absolute bg-[#37393B99] w-[15.764vw] h-[15.395vh] right-[1.667vw] cursor-pointer"
|
||||
>
|
||||
<p className="btns font-medium">Сценарии проживания</p>
|
||||
<img
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function SeasonsCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -9,7 +9,7 @@ export function SeasonsCard({ slide }: { slide: number }) {
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: +(slide > 12),
|
||||
bottom: slide > 12 ? '52.237vh' : '93.684vh',
|
||||
bottom: slide > 12 ? "52.237vh" : "93.684vh",
|
||||
}}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
@@ -17,12 +17,12 @@ export function SeasonsCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Сезонность"
|
||||
title="Сезонность"
|
||||
text="Сезонность даёт дополнительную глубину презентации: покупатель видит, как жилой комплекс выглядит осенью под золотыми листьями или зимой, украшенной гирляндами."
|
||||
packages={['Комфорт+']}
|
||||
packages={["Комфорт+"]}
|
||||
src="/videos/pages/prime/seasons.mp4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-end absolute w-[15.486vw] h-[24.605vh] bg-[url(/img/pages/prime/autumn.jpg)] bg-cover bg-no-repeat bg-center right-[1.667vw]"
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] flex flex-col justify-end absolute w-[15.486vw] h-[24.605vh] bg-[url(/img/pages/prime/autumn.jpg)] bg-cover bg-no-repeat bg-center right-[1.667vw] cursor-pointer"
|
||||
>
|
||||
<p className="btns font-medium">Сезонность</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import GraffIcon from '../../../../../public/icons/graff.svg';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PrimeModal } from '../modals/PrimeModal';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import GraffIcon from "../../../../../public/icons/graff.svg";
|
||||
import { motion } from "framer-motion";
|
||||
import { PrimeModal } from "../modals/PrimeModal";
|
||||
|
||||
export function StreamingCard({ slide }: { slide: number }) {
|
||||
const { setModal } = useModalStore();
|
||||
@@ -15,21 +15,21 @@ export function StreamingCard({ slide }: { slide: number }) {
|
||||
categoryTitle="Удаленная демонстрация"
|
||||
title="Graff.estate stream"
|
||||
text="GRAFF.estate разворачивает «облако» и стримит 3D-сцену на устройство клиента (PC, мобильный), менеджер ведёт экскурсию в режиме реального времени."
|
||||
packages={['Премиум']}
|
||||
packages={["Премиум"]}
|
||||
src="/videos/pages/home/streaming.mp4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
animate={{
|
||||
opacity: +(slide > 10),
|
||||
bottom: slide > 10 ? '0vw' : undefined,
|
||||
bottom: slide > 10 ? "0vw" : undefined,
|
||||
}}
|
||||
className="p-[1.389vw] rounded-[1.389vw] -translate-y-[1.389vw] bg-[#37393B99] backdrop-blur-xs absolute right-[1.667vw] w-[8.958vw] h-[20.263vh] flex flex-col justify-between"
|
||||
>
|
||||
<img src="/img/pages/prime/ruble.png" className="w-[0.833vw]" alt="" />
|
||||
<div className="w-[3.333vw] aspect-square rounded-[1.111vw] bg-[#37393B99] relative self-center">
|
||||
<div className="bg-[#FF4517] w-[0.486vw] aspect-square rounded-full top-[0.556vw] left-[0.556vw] absolute" />
|
||||
<GraffIcon className="text-white w-[1.944vw] h-[1.944vw] absolute top-1/2 left-1/2 -translate-1/2" />
|
||||
<GraffIcon className="text-white w-[1.944vw] h-[1.944vw] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</div>
|
||||
<p className="btns font-medium">Graff.estate stream</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import CloseIcon from '../../../../../public/icons/close.svg';
|
||||
import { categoryDescription } from '@/consts/categories';
|
||||
import { useRef } from 'react';
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import CloseIcon from "../../../../../public/icons/close.svg";
|
||||
import { categoryDescription } from "@/consts/categories";
|
||||
import { useRef } from "react";
|
||||
|
||||
export function PrimeModal({
|
||||
src,
|
||||
@@ -23,7 +23,7 @@ export function PrimeModal({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-[11.458vw] absolute inset-0 z-10 pl-[1.667vw] py-[1.389vw] before:inset-0 before:absolute before:bg-[#0F101199]">
|
||||
<div className="flex flex-col gap-y-[0.417vw] z-1">
|
||||
<div className="flex flex-col gap-y-[0.417vw] z-[1]">
|
||||
<button
|
||||
onClick={() => setModal(null)}
|
||||
className="w-[4.444vw] aspect-square rounded-[0.556vw] bg-white cursor-pointer"
|
||||
@@ -51,8 +51,8 @@ export function PrimeModal({
|
||||
<p
|
||||
key={pack}
|
||||
className={
|
||||
'px-[0.833vw] py-[0.486vw] rounded-[1.181vw] btns font-medium ' +
|
||||
(pack === 'Премиум' ? 'bg-gradient' : 'bg-[#37393B99]')
|
||||
"px-[0.833vw] py-[0.486vw] rounded-[1.181vw] btns font-medium " +
|
||||
(pack === "Премиум" ? "bg-gradient" : "bg-[#37393B99]")
|
||||
}
|
||||
>
|
||||
{pack}
|
||||
@@ -60,11 +60,11 @@ export function PrimeModal({
|
||||
))}
|
||||
</div>
|
||||
<div className="gap-y-[0.278vw] flex flex-col flex-1">
|
||||
{src.endsWith('mp4') ? (
|
||||
{src.endsWith("mp4") ? (
|
||||
categoryDescription[categoryTitle].find(
|
||||
(item) => item.title === title
|
||||
)?.type === 'videoScreen' ? (
|
||||
<div className="bg-[url(/img/pages/home/presentation/touch_screen.png)] bg-no-repeat bg-[length:50%] bg-top relative h-full perspective-[11.5vw]">
|
||||
)?.type === "videoScreen" ? (
|
||||
<div className="bg-[url(/img/pages/home/presentation/touch_screen.png)] bg-no-repeat bg-[length:50%] bg-top relative h-full [perspective:11.5vw]">
|
||||
<video
|
||||
src={src}
|
||||
ref={ref}
|
||||
@@ -72,7 +72,7 @@ export function PrimeModal({
|
||||
autoPlay
|
||||
playsInline
|
||||
loop
|
||||
className="rotate-x-4 left-1/2 -translate-x-1/2 absolute origin-top object-cover object-bottom w-[24.7vw] h-[11.5vw] top-[2.3vw]"
|
||||
className="![rotate:x_4deg] left-1/2 -translate-x-1/2 absolute origin-top object-cover object-bottom w-[24.7vw] h-[11.5vw] top-[2.3vw]"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
import { AnimatePresence, motion, PanInfo } from 'framer-motion';
|
||||
import Close from '../../../../public/icons/close.svg';
|
||||
import Coin from '../../../../public/icons/coin.svg';
|
||||
import { categories, categoryDescription } from '@/consts/categories';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import VideoPrimeModal from './VideoPrimeModal';
|
||||
"use client";
|
||||
import { AnimatePresence, motion, PanInfo } from "framer-motion";
|
||||
import Close from "../../../../public/icons/close.svg";
|
||||
import Coin from "../../../../public/icons/coin.svg";
|
||||
import { categories, categoryDescription } from "@/consts/categories";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import VideoPrimeModal from "./VideoPrimeModal";
|
||||
|
||||
const variantsAnimations = {
|
||||
enter: (direction: number) => {
|
||||
@@ -60,9 +60,9 @@ function CategoryModal({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.body.style.overflow = 'auto';
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -70,26 +70,26 @@ function CategoryModal({
|
||||
<div
|
||||
className={`fixed inset-0 bg-[#0F1011] z-[1] px-2.5 py-4 flex flex-col gap-5 h-full w-full md:p-10 md:gap-10`}
|
||||
>
|
||||
<div className='h-12 flex justify-between items-center'>
|
||||
<div className="h-12 flex justify-between items-center">
|
||||
<h1
|
||||
className={`text-[24px] leading-[0.85] tracking-[-0.04em] w-fit md:text-3xl`}
|
||||
>
|
||||
{titleCategory}
|
||||
</h1>
|
||||
<motion.button
|
||||
className='w-12 h-12 z-[2] bg-[#37393B99] rounded-2xl flex items-center justify-center md:cursor-pointer'
|
||||
className="w-12 h-12 z-[2] bg-[#37393B99] rounded-2xl flex items-center justify-center md:cursor-pointer"
|
||||
onTap={() => setModal(null)}
|
||||
>
|
||||
<Close className='w-5 h-5' />
|
||||
<Close className="w-5 h-5" />
|
||||
</motion.button>
|
||||
</div>
|
||||
<motion.div
|
||||
key={page}
|
||||
initial='enter'
|
||||
animate='center'
|
||||
exit='exit'
|
||||
initial="enter"
|
||||
animate="center"
|
||||
exit="exit"
|
||||
custom={direction}
|
||||
drag='x'
|
||||
drag="x"
|
||||
dragConstraints={{ left: 0, right: 0 }}
|
||||
dragElastic={0.1}
|
||||
onDragEnd={onDragEnd}
|
||||
@@ -97,17 +97,17 @@ function CategoryModal({
|
||||
e.preventDefault();
|
||||
}}
|
||||
variants={variantsAnimations}
|
||||
className='absolute top-20 left-1/2 -translate-x-1/2 w-full flex flex-col items-center md:top-25'
|
||||
className="absolute top-20 left-1/2 -translate-x-1/2 w-full flex flex-col items-center md:top-25"
|
||||
>
|
||||
{description[page].type === 'video' ? (
|
||||
{description[page].type === "video" ? (
|
||||
<video
|
||||
src={description[page].content}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
className='md:w-[60%] md:h-[40vh] max-md:w-[94%] max-md:rounded-2xl'
|
||||
className="md:w-[60%] md:h-[40vh] max-md:w-[94%] max-md:rounded-2xl"
|
||||
></video>
|
||||
) : description[page].type === 'videoScreen' ? (
|
||||
) : description[page].type === "videoScreen" ? (
|
||||
<VideoPrimeModal src={description[page].content} />
|
||||
) : (
|
||||
<div
|
||||
@@ -115,36 +115,36 @@ function CategoryModal({
|
||||
></div>
|
||||
)}
|
||||
|
||||
<div className='bg-[#37393B99] rounded-2xl px-5 py-6 flex flex-col gap-3 w-[95%] '>
|
||||
<h2 className='font-medium text-[20px] leading-[24px] tracking-[-0.02em]'>
|
||||
<div className="bg-[#37393B99] rounded-2xl px-5 py-6 flex flex-col gap-3 w-[95%] ">
|
||||
<h2 className="font-medium text-[20px] leading-[24px] tracking-[-0.02em]">
|
||||
{description[page].title}
|
||||
</h2>
|
||||
<div className='md:flex md:gap-2.5 max-md:flex max-md:flex-col max-md:gap-3'>
|
||||
<p className='font-normal text-[14px] leading-[135%] tracking-[0em] md:flex-1'>
|
||||
<div className="md:flex md:gap-2.5 max-md:flex max-md:flex-col max-md:gap-3">
|
||||
<p className="font-normal text-[14px] leading-[135%] tracking-[0em] md:flex-1">
|
||||
{description[page].text1}
|
||||
</p>
|
||||
<p className='font-normal text-[14px] leading-[135%] tracking-[0em] md:flex-1'>
|
||||
<p className="font-normal text-[14px] leading-[135%] tracking-[0em] md:flex-1">
|
||||
{description[page].text2}
|
||||
</p>
|
||||
</div>
|
||||
<div className='mt-[32px] flex gap-2'>
|
||||
<div className='btns w-fit px-3 py-[7px] rounded-[17px] bg-gradient-to-r from-[#6078F2] via-[#7583f3] to-[#C868F5]'>
|
||||
<div className="mt-[32px] flex gap-2">
|
||||
<div className="btns w-fit px-3 py-[7px] rounded-[17px] bg-gradient-to-r from-[#6078F2] via-[#7583f3] to-[#C868F5]">
|
||||
{description[page].package}
|
||||
</div>
|
||||
{description[page].isOption && (
|
||||
<div className='btns w-fit px-3 py-[7px] rounded-[17px] bg-[#37393B99] flex gap-1 items-center'>
|
||||
<Coin className='w-4 h-4' /> Опция
|
||||
<div className="btns w-fit px-3 py-[7px] rounded-[17px] bg-[#37393B99] flex gap-1 items-center">
|
||||
<Coin className="w-4 h-4" /> Опция
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
{description.length > 1 && (
|
||||
<div className='absolute w-full flex justify-center gap-0.5 bottom-4'>
|
||||
<div className="absolute w-full flex justify-center gap-0.5 bottom-4">
|
||||
{description.map((item, index) => (
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
index === page ? 'bg-white' : 'bg-[#37393B99]'
|
||||
index === page ? "bg-white" : "bg-[#37393B99]"
|
||||
}`}
|
||||
key={index}
|
||||
></div>
|
||||
|
||||
@@ -1,125 +1,125 @@
|
||||
'use client';
|
||||
import { $package } from '@/stores/configurator-store/configurationStore';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { useUnit } from 'effector-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getExampleNumber } from 'libphonenumber-js';
|
||||
import examples from 'libphonenumber-js/mobile/examples';
|
||||
import Link from 'next/link';
|
||||
import { useMemo, useState } from 'react';
|
||||
import ReactInputMask from 'react-input-mask';
|
||||
import { Country } from 'react-phone-number-input';
|
||||
"use client";
|
||||
import { $package } from "@/stores/configurator-store/configurationStore";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { useUnit } from "effector-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { getExampleNumber } from "libphonenumber-js";
|
||||
import examples from "libphonenumber-js/mobile/examples";
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState } from "react";
|
||||
import ReactInputMask from "react-input-mask";
|
||||
import { Country } from "react-phone-number-input";
|
||||
|
||||
function PrimeForm() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { title, cost, time } = useUnit($package);
|
||||
const [[phoneCode, country], setPhoneCodeAndCountry] = useState<
|
||||
[string, Country]
|
||||
>(['+7', 'RU']);
|
||||
>(["+7", "RU"]);
|
||||
const placeholder = useMemo(
|
||||
() =>
|
||||
getExampleNumber(country, examples)
|
||||
?.formatInternational()
|
||||
.split(' ')
|
||||
.split(" ")
|
||||
.slice(1)
|
||||
.join(' '),
|
||||
.join(" "),
|
||||
[country]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed bottom-0 left-1/2 -translate-x-1/2 rounded-2xl p-px z-2 ${
|
||||
className={`fixed bottom-0 left-1/2 -translate-x-1/2 rounded-2xl p-px z-[2] ${
|
||||
visible
|
||||
? 'bg-[#0F101199] backdrop-blur-[16px] w-full h-full rounded-none z-13'
|
||||
: 'bg-gradient-to-r from-white/10 to-white/0'
|
||||
? "bg-[#0F101199] backdrop-blur-[16px] w-full h-full rounded-none z-[13]"
|
||||
: "bg-gradient-to-r from-white/10 to-white/0"
|
||||
}`}
|
||||
onClick={() => setVisible(false)}
|
||||
>
|
||||
<motion.div
|
||||
animate={visible ? 'open' : 'closed'}
|
||||
animate={visible ? "open" : "closed"}
|
||||
variants={{
|
||||
open: { width: '95vw', height: '512px', bottom: '10px' },
|
||||
closed: { width: '240px', height: '50px', bottom: '10px' },
|
||||
open: { width: "95vw", height: "512px", bottom: "10px" },
|
||||
closed: { width: "240px", height: "50px", bottom: "10px" },
|
||||
}}
|
||||
transition={{
|
||||
bounce: 'none',
|
||||
bounce: "none",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setVisible(true);
|
||||
}}
|
||||
className='absolute left-1/2 -translate-x-1/2 bg-[#37393B99] backdrop-blur-2xl rounded-[15px] flex flex-col gap-4'
|
||||
className="absolute left-1/2 -translate-x-1/2 bg-[#37393B99] backdrop-blur-2xl rounded-[15px] flex flex-col gap-4"
|
||||
>
|
||||
<div
|
||||
className={`flex justify-center items-center gap-2 ${
|
||||
visible
|
||||
? 'px-5 mt-6 md:mt-10 md:justify-start md:ml-10 md:px-0'
|
||||
: 'px-5 mt-4'
|
||||
? "px-5 mt-6 md:mt-10 md:justify-start md:ml-10 md:px-0"
|
||||
: "px-5 mt-4"
|
||||
}`}
|
||||
>
|
||||
<p className='font-[20px] leading-[18px] text-nowrap tracking-[-0.02em]'>
|
||||
<p className="font-[20px] leading-[18px] text-nowrap tracking-[-0.02em]">
|
||||
{title}
|
||||
</p>
|
||||
<div className='flex gap-1'>
|
||||
<div className='h-4.5 rounded-[30px] px-2 py-0.5 font-normal text-[10px] leading-[120%] tracking-[-0.01em] bg-[#37393B99] text-nowrap'>
|
||||
<div className="flex gap-1">
|
||||
<div className="h-4.5 rounded-[30px] px-2 py-0.5 font-normal text-[10px] leading-[120%] tracking-[-0.01em] bg-[#37393B99] text-nowrap">
|
||||
~{cost} млн.
|
||||
</div>
|
||||
<div className='h-4.5 rounded-[30px] px-2 py-0.5 font-normal text-[10px] text-[#0F1011] leading-[120%] tracking-[-0.01em] bg-[#B5F54E] text-nowrap'>
|
||||
<div className="h-4.5 rounded-[30px] px-2 py-0.5 font-normal text-[10px] text-[#0F1011] leading-[120%] tracking-[-0.01em] bg-[#B5F54E] text-nowrap">
|
||||
{time} месяцев
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visible && (
|
||||
<>
|
||||
<h1 className='mx-5 h-[90px] font-medium text-[32px] leading-[95%] tracking-[-0.02em] text-center md:mx-10 md:text-left md:w-90 md:h-15'>
|
||||
<h1 className="mx-5 h-[90px] font-medium text-[32px] leading-[95%] tracking-[-0.02em] text-center md:mx-10 md:text-left md:w-90 md:h-15">
|
||||
Оставьте контакты, а мы сразу сформируем КП
|
||||
</h1>
|
||||
<form action='submit' className='flex flex-col gap-3 md:mx-5'>
|
||||
<form action="submit" className="md:mx-5 flex flex-col gap-3">
|
||||
<input
|
||||
id='name'
|
||||
autoComplete='none'
|
||||
type='text'
|
||||
id="name"
|
||||
autoComplete="none"
|
||||
type="text"
|
||||
required
|
||||
placeholder='Имя*'
|
||||
placeholder="Имя*"
|
||||
// {...register('fullname')}
|
||||
className='h-[56px] mx-5 bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all placeholder:btnl btnl placeholder:font-medium placeholder:select-none'
|
||||
className="h-[56px] mx-5 bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all placeholder:btnl btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<div className='h-[56px] mx-5 flex gap-x-3 py-2 border-[#3D425C] relative'>
|
||||
<div className="h-[56px] mx-5 flex gap-x-3 py-2 border-[#3D425C] relative">
|
||||
<ReactInputMask
|
||||
type='tel'
|
||||
autoComplete='none'
|
||||
type="tel"
|
||||
autoComplete="none"
|
||||
onChange={() => {}}
|
||||
id={'tel'}
|
||||
id={"tel"}
|
||||
maskChar={null}
|
||||
mask={'+7 ' + (placeholder?.replace(/\d/g, '9') ?? '')}
|
||||
placeholder='Телефон*'
|
||||
className='placeholder:btnl placeholder:font-medium placeholder:select-none peer btnl w-full h-full transition-all bg-transparent rounded-none outline-none'
|
||||
mask={"+7 " + (placeholder?.replace(/\d/g, "9") ?? "")}
|
||||
placeholder="Телефон*"
|
||||
className="placeholder:btnl placeholder:font-medium placeholder:select-none peer btnl w-full h-full transition-all bg-transparent rounded-none outline-none"
|
||||
/>
|
||||
<div className=' bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2' />
|
||||
<div className=" bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2" />
|
||||
</div>
|
||||
<input
|
||||
autoComplete='none'
|
||||
autoComplete="none"
|
||||
required
|
||||
id='email'
|
||||
type='email'
|
||||
placeholder='E-mail*'
|
||||
className='h-[56px] mx-5 bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all placeholder:btnl placeholder:font-medium placeholder:select-none'
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="E-mail*"
|
||||
className="h-[56px] mx-5 bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
<div className='md:flex mx-5 items-stretch lg:gap-[0.833vw] gap-3'>
|
||||
<div className="md:flex mx-5 items-stretch lg:gap-[0.833vw] gap-3">
|
||||
<Button
|
||||
type='submit'
|
||||
className=' mt-6 btnl max-md:mb-3 max-md:w-full lg:px-[2.222vw] lg:py-[1.389vw] px-8 py-5 cursor-pointer lg:rounded-[1.111vw] rounded-2xl'
|
||||
type="submit"
|
||||
className=" mt-6 btnl max-md:mb-3 max-md:w-full lg:px-[2.222vw] lg:py-[1.389vw] px-8 py-5 cursor-pointer lg:rounded-[1.111vw] rounded-2xl"
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
<Link
|
||||
href={'/policy'}
|
||||
className='text2 mt-3 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1'
|
||||
href={"/policy"}
|
||||
className="text2 mt-3 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1"
|
||||
>
|
||||
<p>
|
||||
<span className='text-[#7A7A7A]'>
|
||||
<span className="text-[#7A7A7A]">
|
||||
*Нажимая кнопку отправить, вы принимаете
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
условия использования и политику конфиденциальности
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { search } from '@/consts/presentation/search';
|
||||
import { useMediaQueries } from '@/hooks/useMediaQueries';
|
||||
import { motion, MotionValue, useTransform } from 'framer-motion';
|
||||
import HeartIcon from '../../../public/icons/hearth.svg';
|
||||
import LocationIcon from '../../../public/icons/location.svg';
|
||||
import { primeVideos, videos } from '@/consts/presentation/videos';
|
||||
import { search } from "@/consts/presentation/search";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { motion, MotionValue, useTransform } from "framer-motion";
|
||||
import HeartIcon from "../../../public/icons/hearth.svg";
|
||||
import LocationIcon from "../../../public/icons/location.svg";
|
||||
import { primeVideos, videos } from "@/consts/presentation/videos";
|
||||
|
||||
export function SearchAndSelect({
|
||||
scrollProgress,
|
||||
@@ -12,7 +12,7 @@ export function SearchAndSelect({
|
||||
}: {
|
||||
scrollProgress: MotionValue<number>;
|
||||
top?: number;
|
||||
page?: 'main' | 'prime';
|
||||
page?: "main" | "prime";
|
||||
}) {
|
||||
const opacityMain = useTransform(
|
||||
scrollProgress,
|
||||
@@ -29,13 +29,13 @@ export function SearchAndSelect({
|
||||
const x = useTransform(
|
||||
scrollProgress,
|
||||
[1 / (primeVideos.length + 7), 2 / (primeVideos.length + 7)],
|
||||
['0%', '-100%']
|
||||
["0%", "-100%"]
|
||||
);
|
||||
|
||||
const y = useTransform(
|
||||
scrollProgress,
|
||||
[0 / (primeVideos.length + 7), 1 / (primeVideos.length + 7)],
|
||||
['100%', '0%']
|
||||
["100%", "0%"]
|
||||
);
|
||||
|
||||
const opacityMini = useTransform(
|
||||
@@ -51,16 +51,16 @@ export function SearchAndSelect({
|
||||
style={
|
||||
isLg
|
||||
? {
|
||||
opacity: page === 'main' ? opacityMain : opacityPrime,
|
||||
y: page === 'prime' ? y : undefined,
|
||||
x: page === 'prime' ? x : undefined,
|
||||
opacity: page === "main" ? opacityMain : opacityPrime,
|
||||
y: page === "prime" ? y : undefined,
|
||||
x: page === "prime" ? x : undefined,
|
||||
}
|
||||
: { top, opacity: opacityMini }
|
||||
}
|
||||
className={`max-md:p-5 flex max-md:flex-col lg:flex-col md:gap-3 max-lg:sticky max-md:gap-y-7 gap-y-3 max-md:rounded-2xl max-md:h-full select-none max-md:[background:radial-gradient(ellipse_at_100%_100%,#7A7A7A33,transparent)] max-md:[backdrop-filter:blur(500px)] lg:absolute ${
|
||||
page === 'main'
|
||||
? 'lg:w-[31.875vw] h-full'
|
||||
: 'lg:w-[23.611vw] h-[40.556vw]'
|
||||
page === "main"
|
||||
? "lg:w-[31.875vw] h-full"
|
||||
: "lg:w-[23.611vw] h-[40.556vw]"
|
||||
}`}
|
||||
>
|
||||
<div className="lg:p-[1.667vw] md:max-lg:p-6 lg:rounded-[1.111vw] md:max-lg:rounded-2xl md:[background:radial-gradient(ellipse_at_100%,#7A7A7A33,transparent)] md:[backdrop-filter:blur(500px)] max-md:space-y-4 md:flex flex-col justify-between md:flex-1">
|
||||
@@ -70,7 +70,7 @@ export function SearchAndSelect({
|
||||
<div className="bg-[#FF4517] lg:rounded-[0.625vw] rounded-[9px] lg:p-[0.556vw] p-1.5">
|
||||
<HeartIcon className="text-[#232425] lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5" />
|
||||
</div>
|
||||
<p className={`text1${page === 'main' ? ' md:max-w-[70%]' : ''}`}>
|
||||
<p className={`text1${page === "main" ? " md:max-w-[70%]" : ""}`}>
|
||||
Эмоциональное вовлечение пользователя в выбор квартиры
|
||||
</p>
|
||||
</div>
|
||||
@@ -78,7 +78,7 @@ export function SearchAndSelect({
|
||||
<div className="bg-[#B5F54E] lg:rounded-[0.625vw] rounded-[9px] lg:p-[0.556vw] p-1.5">
|
||||
<LocationIcon className="text-[#232425] lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5" />
|
||||
</div>
|
||||
<p className={`text1${page === 'main' ? ' md:max-w-[70%]' : ''}`}>
|
||||
<p className={`text1${page === "main" ? " md:max-w-[70%]" : ""}`}>
|
||||
Удобство выбора расположения и видовых характеристик
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import LoaderIcon from '../../../public/icons/loader.svg';
|
||||
import { videos } from '@/consts/presentation/videos';
|
||||
import LoaderIcon from "../../../public/icons/loader.svg";
|
||||
import { videos } from "@/consts/presentation/videos";
|
||||
import {
|
||||
motion,
|
||||
MotionValue,
|
||||
useMotionValueEvent,
|
||||
useTransform,
|
||||
} from 'framer-motion';
|
||||
import { createRef, Fragment, RefObject, useEffect, useState } from 'react';
|
||||
} from "framer-motion";
|
||||
import { createRef, Fragment, RefObject, useEffect, useState } from "react";
|
||||
|
||||
export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const [slide, setSlide] = useState(0);
|
||||
@@ -30,31 +30,31 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const x = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['32.708vw', '32.708vw', '0vw', '0vw', '30vw', '30vw']
|
||||
["32.708vw", "32.708vw", "0vw", "0vw", "30vw", "30vw"]
|
||||
);
|
||||
|
||||
const width = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['64.444vw', '61.667vw', '64.444vw', '64.444vw', '61.667vw', '65vw']
|
||||
["64.444vw", "61.667vw", "64.444vw", "64.444vw", "61.667vw", "65vw"]
|
||||
);
|
||||
|
||||
const backgroundSize = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['48.333vw', '53.962vw', '53.962vw', '62.946vw', '53.962vw', '47.813vw']
|
||||
["48.333vw", "53.962vw", "53.962vw", "62.946vw", "53.962vw", "47.813vw"]
|
||||
);
|
||||
|
||||
const videoWidth = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['39.822vw', '44.46vw', '44.46vw', '51.861vw', '44.46vw', '39.392vw']
|
||||
["39.822vw", "44.46vw", "44.46vw", "51.861vw", "44.46vw", "39.392vw"]
|
||||
);
|
||||
|
||||
const videoHeight = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['19vw', '21vw', '21vw', '24.5vw', '21vw', '18.5vw']
|
||||
["19vw", "21vw", "21vw", "24.5vw", "21vw", "18.5vw"]
|
||||
);
|
||||
|
||||
const opacity = useTransform(scroll, [4.5 / 5, 1], [0, 1]);
|
||||
@@ -62,22 +62,38 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const backgroundPositionY = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['0vw', '0vw', '0vw', '-2.569vw', '0vw', '7.292vw']
|
||||
["0vw", "0vw", "0vw", "-2.569vw", "0vw", "7.292vw"]
|
||||
);
|
||||
|
||||
const top = useTransform(
|
||||
scroll,
|
||||
[0, 1 / 5, 2 / 5, 3 / 5, 4 / 5, 1],
|
||||
['3.602vw', '4.022vw', '4.022vw', '2.122vw', '4.022vw', '10.855vw']
|
||||
["3.602vw", "4.022vw", "4.022vw", "2.122vw", "4.022vw", "10.855vw"]
|
||||
);
|
||||
|
||||
useMotionValueEvent(scroll, 'change', (value) =>
|
||||
useMotionValueEvent(scroll, "change", (value) =>
|
||||
setSlide(Math.min(Math.trunc(value * videos.length), videos.length - 1))
|
||||
);
|
||||
|
||||
const [currentBuffering, setCurrentBuffering] = useState(false);
|
||||
|
||||
useEffect(() => setCurrentBuffering(true), [slide]);
|
||||
useEffect(() => {
|
||||
videoRefs[slide]?.current?.addEventListener("waiting", () =>
|
||||
setCurrentBuffering(true)
|
||||
);
|
||||
videoRefs[slide]?.current?.addEventListener("playing", () =>
|
||||
setCurrentBuffering(false)
|
||||
);
|
||||
|
||||
return () => {
|
||||
videoRefs[slide]?.current?.removeEventListener("waiting", () =>
|
||||
setCurrentBuffering(false)
|
||||
);
|
||||
videoRefs[slide]?.current?.removeEventListener("playing", () =>
|
||||
setCurrentBuffering(false)
|
||||
);
|
||||
};
|
||||
}, [slide, videoRefs]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -90,7 +106,7 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
}}
|
||||
className="absolute h-full overflow-hidden bg-[url(/img/pages/home/presentation/touch_screen.png)] bg-cover bg-top bg-no-repeat"
|
||||
onViewportEnter={handleOnViewportFeatureEnter}
|
||||
viewport={{ margin: '-10% 0% 0% 0%', once: true }}
|
||||
viewport={{ margin: "-10% 0% 0% 0%", once: true }}
|
||||
>
|
||||
{!!videoRefs.length &&
|
||||
videos.map(({ src }, index) => (
|
||||
@@ -106,12 +122,10 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
)}
|
||||
<motion.video
|
||||
key={src}
|
||||
onWaiting={() => setCurrentBuffering(true)}
|
||||
onPlaying={() => setCurrentBuffering(false)}
|
||||
src={
|
||||
isViewportEntered
|
||||
? `/videos/pages/home/presentation/${src}.mp4`
|
||||
: ''
|
||||
: ""
|
||||
}
|
||||
style={{
|
||||
zIndex: videos.length - index,
|
||||
@@ -123,8 +137,8 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className={`object-bottom object-cover origin-top absolute left-1/2 -translate-x-1/2 !rotate-x-[4deg] transition-[opacity,transform]${
|
||||
slide > index ? ' opacity-0' : ''
|
||||
className={`object-bottom object-cover origin-top absolute left-1/2 -translate-x-1/2 [rotate:x_4deg] transition-[opacity,transform]${
|
||||
slide > index ? " opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<motion.div
|
||||
@@ -134,7 +148,7 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
height: videoHeight,
|
||||
top,
|
||||
}}
|
||||
className="object-bottom object-cover origin-top absolute left-1/2 bg-black/50 -translate-x-1/2 !rotate-x-[4deg] flex justify-center items-center"
|
||||
className="object-bottom object-cover origin-top absolute left-1/2 bg-black/50 -translate-x-1/2 ![rotate:x_4deg] flex justify-center items-center"
|
||||
animate={{
|
||||
opacity: currentBuffering ? 1 : 0,
|
||||
}}
|
||||
@@ -145,7 +159,7 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="w-full h-[5.556vw] bg-gradient-to-t from-[#0F1011] absolute -bottom-1"
|
||||
className="w-full h-[5.556vw] bg-gradient-to-t from-[#0F1011] absolute -bottom-0"
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import LoaderIcon from '../../../public/icons/loader.svg';
|
||||
import { primeVideos } from '@/consts/presentation/videos';
|
||||
import LoaderIcon from "../../../public/icons/loader.svg";
|
||||
import { primeVideos } from "@/consts/presentation/videos";
|
||||
import {
|
||||
motion,
|
||||
MotionValue,
|
||||
useMotionValueEvent,
|
||||
useTransform,
|
||||
} from 'framer-motion';
|
||||
import { createRef, RefObject, useEffect, useState } from 'react';
|
||||
} from "framer-motion";
|
||||
import { createRef, Fragment, RefObject, useEffect, useState } from "react";
|
||||
|
||||
export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const [slide, setSlide] = useState(0);
|
||||
@@ -22,7 +22,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
videoRefs[slide].current.play();
|
||||
}, [slide, videoRefs]);
|
||||
|
||||
useMotionValueEvent(scroll, 'change', (value) =>
|
||||
useMotionValueEvent(scroll, "change", (value) =>
|
||||
setSlide(
|
||||
Math.min(
|
||||
Math.trunc(value * (primeVideos.length + 9)),
|
||||
@@ -41,7 +41,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
const y = useTransform(
|
||||
scroll,
|
||||
[6 / (primeVideos.length + 7), 7 / (primeVideos.length + 7)],
|
||||
['0%', '-100%']
|
||||
["0%", "-100%"]
|
||||
);
|
||||
|
||||
const x = useTransform(
|
||||
@@ -55,7 +55,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
5 / (primeVideos.length + 7),
|
||||
6 / (primeVideos.length + 7),
|
||||
],
|
||||
['32.708vw', '32.708vw', '0vw', '0vw', '30vw', '30vw', '0vw']
|
||||
["32.708vw", "32.708vw", "0vw", "0vw", "30vw", "30vw", "0vw"]
|
||||
);
|
||||
|
||||
const width = useTransform(
|
||||
@@ -68,7 +68,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
4 / (primeVideos.length + 7),
|
||||
5 / (primeVideos.length + 7),
|
||||
],
|
||||
['64.444vw', '61.667vw', '64.444vw', '64.444vw', '61.667vw', '65vw']
|
||||
["64.444vw", "61.667vw", "64.444vw", "64.444vw", "61.667vw", "65vw"]
|
||||
);
|
||||
|
||||
const backgroundSize = useTransform(
|
||||
@@ -81,7 +81,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
4 / (primeVideos.length + 7),
|
||||
5 / (primeVideos.length + 7),
|
||||
],
|
||||
['48.333vw', '53.962vw', '53.962vw', '62.946vw', '47.813vw', '53.962vw']
|
||||
["48.333vw", "53.962vw", "53.962vw", "62.946vw", "47.813vw", "53.962vw"]
|
||||
);
|
||||
|
||||
const videoWidth = useTransform(
|
||||
@@ -94,7 +94,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
4 / (primeVideos.length + 7),
|
||||
5 / (primeVideos.length + 7),
|
||||
],
|
||||
['39.822vw', '44.46vw', '44.46vw', '51.861vw', '39.392vw', '44.46vw']
|
||||
["39.822vw", "44.46vw", "44.46vw", "51.861vw", "39.392vw", "44.46vw"]
|
||||
);
|
||||
|
||||
const opacity = useTransform(
|
||||
@@ -117,7 +117,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
4 / (primeVideos.length + 7),
|
||||
5 / (primeVideos.length + 7),
|
||||
],
|
||||
['19vw', '21vw', '21vw', '24.5vw', '18.5vw', '21vw']
|
||||
["19vw", "21vw", "21vw", "24.5vw", "18.5vw", "21vw"]
|
||||
);
|
||||
|
||||
const backgroundPositionY = useTransform(
|
||||
@@ -130,7 +130,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
4 / (primeVideos.length + 7),
|
||||
5 / (primeVideos.length + 7),
|
||||
],
|
||||
['0vw', '0vw', '0vw', '-2.569vw', '7.292vw', '0vw']
|
||||
["0vw", "0vw", "0vw", "-2.569vw", "7.292vw", "0vw"]
|
||||
);
|
||||
|
||||
const top = useTransform(
|
||||
@@ -143,14 +143,25 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
4 / (primeVideos.length + 7),
|
||||
5 / (primeVideos.length + 7),
|
||||
],
|
||||
['3.602vw', '4.022vw', '4.022vw', '2.122vw', '10.855vw', '4.022vw']
|
||||
["3.602vw", "4.022vw", "4.022vw", "2.122vw", "10.855vw", "4.022vw"]
|
||||
);
|
||||
|
||||
const [currentBuffering, setCurrentBuffering] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentBuffering(true);
|
||||
}, [slide]);
|
||||
if (!videoRefs[slide] || !videoRefs[slide].current) return;
|
||||
const video = videoRefs[slide].current;
|
||||
video.addEventListener("waiting", () => setCurrentBuffering(true));
|
||||
|
||||
videoRefs[slide].current.addEventListener("playing", () =>
|
||||
setCurrentBuffering(false)
|
||||
);
|
||||
|
||||
return () => {
|
||||
video.removeEventListener("waiting", () => setCurrentBuffering(false));
|
||||
video.removeEventListener("playing", () => setCurrentBuffering(false));
|
||||
};
|
||||
}, [slide, videoRefs]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -162,14 +173,14 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
backgroundSize,
|
||||
perspective: videoHeight,
|
||||
}}
|
||||
viewport={{ margin: '-10% 0% 0% 0%', once: true }}
|
||||
viewport={{ margin: "-10% 0% 0% 0%", once: true }}
|
||||
onViewportEnter={handleOnViewportFeatureEnter}
|
||||
className="absolute overflow-hidden h-[40.556vw] bg-[url(/img/pages/home/presentation/touch_screen.png)] bg-no-repeat bg-[length:48.333vw] bg-top"
|
||||
>
|
||||
{!!videoRefs.length &&
|
||||
primeVideos.map(({ src }, index) =>
|
||||
src ? (
|
||||
<>
|
||||
<Fragment key={index}>
|
||||
{index === slide && slide > 2 && (
|
||||
<motion.p
|
||||
style={{ opacity }}
|
||||
@@ -184,7 +195,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
src={
|
||||
isViewportEntered
|
||||
? `/videos/pages/home/presentation/${src}.mp4`
|
||||
: ''
|
||||
: ""
|
||||
}
|
||||
ref={videoRefs[index]}
|
||||
loop
|
||||
@@ -196,12 +207,10 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
height: videoHeight,
|
||||
top,
|
||||
}}
|
||||
onWaiting={() => setCurrentBuffering(true)}
|
||||
onPlaying={() => setCurrentBuffering(false)}
|
||||
className={`absolute w-[39.822vw] h-[19vw] origin-top object-bottom object-cover top-[3.602vw] !rotate-x-4 left-1/2 -translate-x-1/2 transition-opacity${
|
||||
className={`absolute w-[39.822vw] h-[19vw] origin-top object-bottom object-cover top-[3.602vw] ![rotate:x_4deg] left-1/2 -translate-x-1/2 transition-opacity${
|
||||
slide > index && slide < primeVideos.length
|
||||
? ' opacity-0'
|
||||
: ''
|
||||
? " opacity-0"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
<motion.div
|
||||
@@ -211,7 +220,7 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
height: videoHeight,
|
||||
top,
|
||||
}}
|
||||
className="object-bottom object-cover origin-top absolute left-1/2 bg-black/50 -translate-x-1/2 !rotate-x-[4deg] flex justify-center items-center"
|
||||
className="object-bottom object-cover origin-top absolute left-1/2 bg-black/50 -translate-x-1/2 ![rotate:x_4deg] flex justify-center items-center"
|
||||
animate={{
|
||||
opacity: currentBuffering ? 1 : 0,
|
||||
}}
|
||||
@@ -220,13 +229,13 @@ export function VideoLayerPrime({ scroll }: { scroll: MotionValue<number> }) {
|
||||
<span className="text2 select-none">Загружаем видео...</span>
|
||||
</motion.div>
|
||||
<div className="h-[5.5vw] w-full bg-gradient-to-t from-[#0f1011] via-75% left-1/2 -translate-x-1/2 bottom-0 absolute" />
|
||||
</>
|
||||
</Fragment>
|
||||
) : (
|
||||
<div
|
||||
key={index}
|
||||
style={{ zIndex: primeVideos.length + 7 - index }}
|
||||
className={`inset-0 transition-opacity${
|
||||
slide > index ? ' opacity-0' : ''
|
||||
slide > index ? " opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -5,175 +5,175 @@ export type SourceImageCategory = {
|
||||
};
|
||||
export interface ImagesCategories {
|
||||
Оборудование: SourceImageCategory[];
|
||||
'Детальная проработка окружения': SourceImageCategory[];
|
||||
'Дизайн интерьеров': SourceImageCategory[];
|
||||
'Рекламные материалы': SourceImageCategory[];
|
||||
"Детальная проработка окружения": SourceImageCategory[];
|
||||
"Дизайн интерьеров": SourceImageCategory[];
|
||||
"Рекламные материалы": SourceImageCategory[];
|
||||
Опции: SourceImageCategory[];
|
||||
Сезонность: SourceImageCategory[];
|
||||
'Удаленная демонстрация': SourceImageCategory[];
|
||||
"Удаленная демонстрация": SourceImageCategory[];
|
||||
}
|
||||
|
||||
export const imagesCategories: ImagesCategories = {
|
||||
Оборудование: [
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-[left_4px_top_2px] bg-[url(/img/pages/prime/wallPanel.png)] md:bg-[length:90%] md:bg-contain md:bg-top',
|
||||
"bg-no-repeat bg-cover bg-[left_4px_top_2px] bg-[url(/img/pages/prime/wallPanel.png)] md:bg-[length:90%] md:bg-contain md:bg-top",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-[left_6px_top_6px] bg-[url(/img/pages/prime/brandTablet800.png)] md:bg-[length:90%] md:bg-contain md:bg-center',
|
||||
"bg-no-repeat bg-cover bg-[left_6px_top_6px] bg-[url(/img/pages/prime/brandTablet800.png)] md:bg-[length:90%] md:bg-contain md:bg-center",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-[left_6px_top_6px] bg-[url(/img/pages/prime/brandTablet800.png)] md:bg-[length:90%] md:bg-contain md:bg-center',
|
||||
"bg-no-repeat bg-cover bg-[left_6px_top_6px] bg-[url(/img/pages/prime/brandTablet800.png)] md:bg-[length:90%] md:bg-contain md:bg-center",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
],
|
||||
'Детальная проработка окружения': [
|
||||
"Детальная проработка окружения": [
|
||||
{
|
||||
className: '',
|
||||
className: "",
|
||||
source: [],
|
||||
childrenClassName: 'w-14 h-14 rounded-full top-2.5',
|
||||
childrenClassName: "w-14 h-14 rounded-full top-2.5",
|
||||
},
|
||||
],
|
||||
'Дизайн интерьеров': [
|
||||
"Дизайн интерьеров": [
|
||||
{
|
||||
className: '',
|
||||
source: ['designInterior1.jpg'],
|
||||
className: "",
|
||||
source: ["designInterior1.jpg"],
|
||||
childrenClassName:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior1.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior1.jpg)]",
|
||||
},
|
||||
{
|
||||
className: '',
|
||||
source: ['designInterior2.jpg'],
|
||||
className: "",
|
||||
source: ["designInterior2.jpg"],
|
||||
childrenClassName:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior2.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior2.jpg)]",
|
||||
},
|
||||
{
|
||||
className: '',
|
||||
source: ['designInterior3.jpg'],
|
||||
className: "",
|
||||
source: ["designInterior3.jpg"],
|
||||
childrenClassName:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior3.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior3.jpg)]",
|
||||
},
|
||||
{
|
||||
className: '',
|
||||
className: "",
|
||||
source: [
|
||||
'designInterior4.jpg',
|
||||
'designInterior4.jpg',
|
||||
'designInterior4.jpg',
|
||||
"designInterior4.jpg",
|
||||
"designInterior4.jpg",
|
||||
"designInterior4.jpg",
|
||||
],
|
||||
childrenClassName:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior4.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/designInterior4.jpg)]",
|
||||
},
|
||||
],
|
||||
'Рекламные материалы': [
|
||||
"Рекламные материалы": [
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-contain bg-[top_8px_center] bg-[url(/img/pages/prime/architecture.png)] md:bg-center_top',
|
||||
source: [''],
|
||||
childrenClassName: '',
|
||||
"bg-no-repeat bg-contain bg-[top_8px_center] bg-[url(/img/pages/prime/architecture.png)] md:bg-center_top",
|
||||
source: [""],
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-[length:57px_112px] bg-center bg-[url(/img/pages/prime/phone.png)] md:bg-[length:70px_140px]',
|
||||
source: [''],
|
||||
childrenClassName: '',
|
||||
"bg-no-repeat bg-[length:57px_112px] bg-center bg-[url(/img/pages/prime/phone.png)] md:bg-[length:70px_140px]",
|
||||
source: [""],
|
||||
childrenClassName: "",
|
||||
},
|
||||
],
|
||||
Опции: [
|
||||
{
|
||||
className: '',
|
||||
source: ['scenario.png'],
|
||||
childrenClassName: 'w-34 h-19.25 top-1/2 -translate-y-1/2',
|
||||
className: "",
|
||||
source: ["scenario.png"],
|
||||
childrenClassName: "w-34 h-19.25 top-1/2 -translate-y-1/2",
|
||||
},
|
||||
{
|
||||
className: '',
|
||||
source: ['avatar.png'],
|
||||
childrenClassName: 'w-20 h-20 top-2.5 rounded-full',
|
||||
className: "",
|
||||
source: ["avatar.png"],
|
||||
childrenClassName: "w-20 h-20 top-2.5 rounded-full",
|
||||
},
|
||||
{
|
||||
className: '',
|
||||
source: ['configuratorInterier1.jpg', 'configuratorInterier2.jpg'],
|
||||
className: "",
|
||||
source: ["configuratorInterier1.jpg", "configuratorInterier2.jpg"],
|
||||
childrenClassName:
|
||||
'w-16 h-16 mt-5 rounded-full [&:nth-child(1)]:translate-x-[-100%] [&:nth-child(2)]:translate-x-[0%]',
|
||||
"w-16 h-16 mt-5 rounded-full [&:nth-child(1)]:translate-x-[-100%] [&:nth-child(2)]:translate-x-[0%]",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-[length:60%] bg-center bg-[top_29px_left_21px] bg-[url(/img/pages/prime/vr.png)]',
|
||||
"bg-no-repeat bg-[length:60%] bg-center bg-[top_29px_left_21px] bg-[url(/img/pages/prime/vr.png)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat z-1 bg-[24px_35px] bg-[url(/img/pages/prime/finance.png)] bg-no-repeat bg-contain before:absolute before:top-[35px] before:left-[24px] before:right-0 before:bottom-0 before:-z-10 before:bg-gradient-to-b before:from-[rgba(39,40,42,0)] before:via-[rgba(39,40,42,0.807)] before:to-[#27282A] before:content-[""] before:rounded-[13px] before:rounded-l-none before:rounded-t-none md:bg-[right_0px_top_35px]',
|
||||
'bg-no-repeat z-[1] bg-[24px_35px] bg-[url(/img/pages/prime/finance.png)] bg-no-repeat bg-contain before:absolute before:top-[35px] before:left-[24px] before:right-0 before:bottom-0 before:-z-10 before:bg-gradient-to-b before:from-[rgba(39,40,42,0)] before:via-[rgba(39,40,42,0.807)] before:to-[#27282A] before:content-[""] before:rounded-[13px] before:rounded-l-none before:rounded-t-none md:bg-[right_0px_top_35px]',
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-[left_20%_top_1px] bg-no-repeat bg-contain bg-center bg-[url(/img/pages/prime/interactiveWindow.png)]',
|
||||
"bg-[left_20%_top_1px] bg-no-repeat bg-contain bg-center bg-[url(/img/pages/prime/interactiveWindow.png)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className: '',
|
||||
className: "",
|
||||
source: [
|
||||
'moduleEngineer1.jpg',
|
||||
'moduleEngineer2.jpg',
|
||||
'moduleEngineer3.jpg',
|
||||
"moduleEngineer1.jpg",
|
||||
"moduleEngineer2.jpg",
|
||||
"moduleEngineer3.jpg",
|
||||
],
|
||||
childrenClassName: 'w-14 h-14 rounded-full top-8',
|
||||
childrenClassName: "w-14 h-14 rounded-full top-8",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat z-1 bg-[length:140%] bg-[url(/img/pages/prime/wheel.png)] before:absolute before:top-[35px] before:left-[24px] before:right-0 before:bottom-0 before:-z-10 before:bg-gradient-to-b before:from-[rgba(39,40,42,0)] before:via-[rgba(39,40,42,0.807)] before:to-[#27282A] before:content-[""] before:rounded-2xl',
|
||||
'bg-no-repeat z-[1] bg-[length:140%] bg-[url(/img/pages/prime/wheel.png)] before:absolute before:top-[35px] before:left-[24px] before:right-0 before:bottom-0 before:-z-10 before:bg-gradient-to-b before:from-[rgba(39,40,42,0)] before:via-[rgba(39,40,42,0.807)] before:to-[#27282A] before:content-[""] before:rounded-2xl',
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-[left_11%_top_1px]m bg-top bg-no-repeat bg-[length:77%] bg-[url(/img/pages/prime/analyse.png)]',
|
||||
"bg-[left_11%_top_1px]m bg-top bg-no-repeat bg-[length:77%] bg-[url(/img/pages/prime/analyse.png)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
],
|
||||
Сезонность: [
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/summer.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/summer.jpg)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/winter.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/winter.jpg)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/spring.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/spring.jpg)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/autumn.jpg)]',
|
||||
"bg-no-repeat bg-cover bg-center bg-[url(/img/pages/prime/autumn.jpg)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
],
|
||||
'Удаленная демонстрация': [
|
||||
"Удаленная демонстрация": [
|
||||
{
|
||||
className:
|
||||
'bg-no-repeat bg-[length:298px_169px] h-[219px] bg-center bg-[url(/img/pages/home/motivation/remote_demo.png)]',
|
||||
"bg-no-repeat bg-[length:298px_169px] h-[219px] bg-center bg-[url(/img/pages/home/motivation/remote_demo.png)]",
|
||||
source: [],
|
||||
childrenClassName: '',
|
||||
childrenClassName: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RefObject, useCallback, useEffect, useState } from 'react';
|
||||
import { RefObject, useCallback, useEffect, useState } from "react";
|
||||
|
||||
export function useScroll(ref: RefObject<HTMLElement>) {
|
||||
const [scroll, setScroll] = useState(
|
||||
@@ -11,9 +11,9 @@ export function useScroll(ref: RefObject<HTMLElement>) {
|
||||
}, [ref]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', handleScroll);
|
||||
document.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => document.removeEventListener('scroll', handleScroll);
|
||||
return () => document.removeEventListener("scroll", handleScroll);
|
||||
}, [handleScroll, ref]);
|
||||
|
||||
return scroll;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
|
||||
export function useWindowWidth() {
|
||||
const [width, setWidth] = useState(window.innerWidth);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const updateWidth = () => setWidth(window.innerWidth);
|
||||
window.addEventListener('resize', updateWidth);
|
||||
window.addEventListener("resize", updateWidth);
|
||||
updateWidth();
|
||||
return () => window.removeEventListener('resize', updateWidth);
|
||||
return () => window.removeEventListener("resize", updateWidth);
|
||||
}, []);
|
||||
|
||||
return width;
|
||||
|
||||
+39
-39
@@ -1,55 +1,55 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Editor, IAllProps } from '@tinymce/tinymce-react';
|
||||
import { Editor, IAllProps } from "@tinymce/tinymce-react";
|
||||
// TinyMCE so the global var exists
|
||||
import 'tinymce/tinymce';
|
||||
import "tinymce/tinymce";
|
||||
// DOM model
|
||||
import 'tinymce/models/dom/model';
|
||||
import "tinymce/models/dom/model";
|
||||
// Theme
|
||||
import 'tinymce/themes/silver';
|
||||
import "tinymce/themes/silver";
|
||||
// Toolbar icons
|
||||
import 'tinymce/icons/default';
|
||||
import "tinymce/icons/default";
|
||||
// Editor styles
|
||||
import 'tinymce/skins/ui/oxide/skin';
|
||||
import "tinymce/skins/ui/oxide/skin";
|
||||
|
||||
// importing the plugin js.
|
||||
// if you use a plugin that is not listed here the editor will fail to load
|
||||
import 'tinymce/plugins/advlist';
|
||||
import 'tinymce/plugins/anchor';
|
||||
import 'tinymce/plugins/autolink';
|
||||
import 'tinymce/plugins/autoresize';
|
||||
import 'tinymce/plugins/autosave';
|
||||
import 'tinymce/plugins/charmap';
|
||||
import 'tinymce/plugins/code';
|
||||
import 'tinymce/plugins/codesample';
|
||||
import 'tinymce/plugins/directionality';
|
||||
import 'tinymce/plugins/emoticons';
|
||||
import 'tinymce/plugins/fullscreen';
|
||||
import 'tinymce/plugins/help';
|
||||
import 'tinymce/plugins/help/js/i18n/keynav/en';
|
||||
import 'tinymce/plugins/image';
|
||||
import 'tinymce/plugins/importcss';
|
||||
import 'tinymce/plugins/insertdatetime';
|
||||
import 'tinymce/plugins/link';
|
||||
import 'tinymce/plugins/lists';
|
||||
import 'tinymce/plugins/media';
|
||||
import 'tinymce/plugins/nonbreaking';
|
||||
import 'tinymce/plugins/pagebreak';
|
||||
import 'tinymce/plugins/preview';
|
||||
import 'tinymce/plugins/quickbars';
|
||||
import 'tinymce/plugins/save';
|
||||
import 'tinymce/plugins/searchreplace';
|
||||
import 'tinymce/plugins/table';
|
||||
import 'tinymce/plugins/visualblocks';
|
||||
import 'tinymce/plugins/visualchars';
|
||||
import 'tinymce/plugins/wordcount';
|
||||
import "tinymce/plugins/advlist";
|
||||
import "tinymce/plugins/anchor";
|
||||
import "tinymce/plugins/autolink";
|
||||
import "tinymce/plugins/autoresize";
|
||||
import "tinymce/plugins/autosave";
|
||||
import "tinymce/plugins/charmap";
|
||||
import "tinymce/plugins/code";
|
||||
import "tinymce/plugins/codesample";
|
||||
import "tinymce/plugins/directionality";
|
||||
import "tinymce/plugins/emoticons";
|
||||
import "tinymce/plugins/fullscreen";
|
||||
import "tinymce/plugins/help";
|
||||
import "tinymce/plugins/help/js/i18n/keynav/en";
|
||||
import "tinymce/plugins/image";
|
||||
import "tinymce/plugins/importcss";
|
||||
import "tinymce/plugins/insertdatetime";
|
||||
import "tinymce/plugins/link";
|
||||
import "tinymce/plugins/lists";
|
||||
import "tinymce/plugins/media";
|
||||
import "tinymce/plugins/nonbreaking";
|
||||
import "tinymce/plugins/pagebreak";
|
||||
import "tinymce/plugins/preview";
|
||||
import "tinymce/plugins/quickbars";
|
||||
import "tinymce/plugins/save";
|
||||
import "tinymce/plugins/searchreplace";
|
||||
import "tinymce/plugins/table";
|
||||
import "tinymce/plugins/visualblocks";
|
||||
import "tinymce/plugins/visualchars";
|
||||
import "tinymce/plugins/wordcount";
|
||||
|
||||
// importing plugin resources
|
||||
import 'tinymce/plugins/emoticons/js/emojis';
|
||||
import "tinymce/plugins/emoticons/js/emojis";
|
||||
|
||||
// Content styles, including inline UI like fake cursors
|
||||
import 'tinymce/skins/content/default/content';
|
||||
import 'tinymce/skins/ui/oxide/content';
|
||||
import "tinymce/skins/content/default/content";
|
||||
import "tinymce/skins/ui/oxide/content";
|
||||
|
||||
export function BundledEditor(props: IAllProps) {
|
||||
return <Editor licenseKey="gpl" {...props} />;
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import ReactLenis, { LenisRef } from 'lenis/react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { PropsWithChildren, useEffect, useRef } from 'react';
|
||||
import ReactLenis, { Lenis, LenisRef } from "lenis/react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
createRef,
|
||||
PropsWithChildren,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
|
||||
export function LenisProvider({ children }: PropsWithChildren) {
|
||||
const lenis = useRef<LenisRef>(null);
|
||||
const { modal } = useModalStore();
|
||||
const [lenisKey, setLenisKey] = useState(0);
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -25,9 +34,18 @@ export function LenisProvider({ children }: PropsWithChildren) {
|
||||
[pathname]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (modal) {
|
||||
lenis.current?.lenis?.destroy();
|
||||
} else {
|
||||
setLenisKey((prev) => prev + 1);
|
||||
}
|
||||
}, [modal]);
|
||||
|
||||
return (
|
||||
<ReactLenis
|
||||
root={!pathname.startsWith('/blog/')}
|
||||
key={lenisKey}
|
||||
root={!pathname.startsWith("/blog/")}
|
||||
className="relative"
|
||||
options={{ autoRaf: false, overscroll: false }}
|
||||
ref={lenis}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Dispatch, SetStateAction, useEffect, useRef } from 'react';
|
||||
import { useHover } from 'usehooks-ts';
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Dispatch, SetStateAction, useEffect, useRef } from "react";
|
||||
import { useHover } from "usehooks-ts";
|
||||
|
||||
export function PrimeProgressItem({
|
||||
active,
|
||||
@@ -40,26 +40,26 @@ export function PrimeProgressItem({
|
||||
return (
|
||||
<motion.div
|
||||
animate={canAnimate ? { x, y, rotateZ, zIndex } : {}}
|
||||
transition={{ bounce: 'none' }}
|
||||
transition={{ bounce: "none" }}
|
||||
className="group max-lg:hidden flex items-center z-10 relative"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ opacity: +!canAnimate }}
|
||||
className={
|
||||
'aspect-[12/1] w-[0.833vw] group-first:hidden ' +
|
||||
"aspect-[12/1] w-[0.833vw] group-first:hidden " +
|
||||
(!active
|
||||
? 'bg-[#37393B]'
|
||||
: 'bg-gradient-to-r from-[#37393B] to-white')
|
||||
? "bg-[#37393B]"
|
||||
: "bg-gradient-to-r from-[#37393B] to-white")
|
||||
}
|
||||
/>
|
||||
<motion.div
|
||||
onClick={onClick}
|
||||
animate={{
|
||||
borderColor: canAnimate
|
||||
? '#37393B00'
|
||||
? "#37393B00"
|
||||
: !active
|
||||
? '#37393B'
|
||||
: '#FFFFFF',
|
||||
? "#37393B"
|
||||
: "#FFFFFF",
|
||||
}}
|
||||
ref={ref}
|
||||
className="p-[0.278vw] border relative cursor-pointer rounded-[1.111vw]"
|
||||
@@ -73,10 +73,10 @@ export function PrimeProgressItem({
|
||||
<motion.div
|
||||
animate={{ opacity: +!canAnimate }}
|
||||
className={
|
||||
'aspect-[12/1] w-[0.833vw] group-last:hidden transition-colors ' +
|
||||
"aspect-[12/1] w-[0.833vw] group-last:hidden transition-colors " +
|
||||
(!active
|
||||
? 'bg-[#37393B]'
|
||||
: 'bg-gradient-to-r from-white to-[#37393B]')
|
||||
? "bg-[#37393B]"
|
||||
: "bg-gradient-to-r from-white to-[#37393B]")
|
||||
}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
@@ -88,10 +88,10 @@ export function PrimeProgressItem({
|
||||
y: active ? 0 : 100,
|
||||
}}
|
||||
exit={{ opacity: 0, y: 100 }}
|
||||
transition={{ bounce: 'none' }}
|
||||
className="flex flex-col items-center gap-[0.278vw] absolute -top-1/2 left-1/2 min-w-full -z-5 -translate-x-1/2 group-last:translate-x-[calc(-50%+0.4165vw)] group-first:translate-x-[calc(-50%-0.4165vw)]"
|
||||
transition={{ bounce: "none" }}
|
||||
className="flex flex-col items-center gap-[0.278vw] absolute -top-1/2 left-1/2 min-w-full -z-[5] !-translate-x-1/2 group-last:![transform:translateX(calc(-50%_+_0.4165vw))] group-first:![transform:translateX(calc(-50%_-_0.4165vw))]"
|
||||
>
|
||||
<p className="btnm font-medium text-centera text-nowrap">{title}</p>
|
||||
<p className="btnm font-medium text-nowrap">{title}</p>
|
||||
<motion.div className="w-[0.069vw] h-[0.556vw] rounded-[0.139vw] bg-[#7A7A7A]" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import MuteIcon from '../../public/icons/mute.svg';
|
||||
import UnmuteIcon from '../../public/icons/unmute.svg';
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import MuteIcon from "../../public/icons/mute.svg";
|
||||
import UnmuteIcon from "../../public/icons/unmute.svg";
|
||||
|
||||
export function VideoMutingBtn({
|
||||
handleClick,
|
||||
@@ -15,18 +15,18 @@ export function VideoMutingBtn({
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
el?.addEventListener('mousemove', (e) => setPoint([e.clientX, e.clientY]));
|
||||
el?.addEventListener("mousemove", (e) => setPoint([e.clientX, e.clientY]));
|
||||
|
||||
return () => {
|
||||
el?.removeEventListener('mousemove', (e) =>
|
||||
el?.removeEventListener("mousemove", (e) =>
|
||||
setPoint([e.clientX, e.clientY])
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="absolute left-0 top-0 h-5/6 w-full z-7">
|
||||
<div ref={ref} className="relative w-full h-full group">
|
||||
<div className="absolute left-0 top-0 h-5/6 w-full z-[7]">
|
||||
<div ref={ref} className="group relative w-full h-full">
|
||||
<button
|
||||
className="bg-[#37393B99] p-[1.736vw] [backdrop-filter:blur(30.72px)] rounded-full group-hover:opacity-100 transition-opacity group-hover:cursor-none opacity-0 sticky outline-none"
|
||||
style={{ left: point[0] - 32, top: point[1] - 32 }}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
|
||||
theme: {
|
||||
screens: {
|
||||
xs: "360px",
|
||||
sm: "640px",
|
||||
md: "768px",
|
||||
lg: "1440px",
|
||||
"2xl": "1536px",
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import postcss from 'postcss';
|
||||
import { type Config } from 'tailwindcss';
|
||||
|
||||
const config: Config = {
|
||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
// fontSize: {
|
||||
// line1: 'line1',
|
||||
// line2: 'line2',
|
||||
// },
|
||||
screens: {
|
||||
'desktop-figma': '1600px',
|
||||
},
|
||||
backgroundImage: {
|
||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||
'gradient-conic':
|
||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
},
|
||||
animation: {
|
||||
'infinite-scroll': 'infinite-scroll 45s linear infinite',
|
||||
'highlight-product': 'highlight-product 0.1s ease-in 0s',
|
||||
},
|
||||
keyframes: {
|
||||
'infinite-scroll': {
|
||||
from: { transform: 'translateX(0%)' },
|
||||
to: { transform: 'translateX(-100%)' },
|
||||
},
|
||||
'highlight-product': {
|
||||
'100%': {
|
||||
backgroundImage: 'url(/img/components/products/highlight.svg)',
|
||||
},
|
||||
},
|
||||
scaling: {
|
||||
'0%': {
|
||||
transform: 'min-width 31.6vw min-height 31.8vw',
|
||||
transition: 'transform 500ms',
|
||||
},
|
||||
'100%': {
|
||||
transform: 'min-width 48vw min-height 48vw',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
function ({ addBase }: { addBase: any }) {
|
||||
const preflightStyles = postcss.parse(
|
||||
fs.readFileSync(
|
||||
require.resolve('tailwindcss/lib/css/preflight.css'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
preflightStyles.walkRules((rule) => {
|
||||
rule.selector = '.no-tailwind-base ' + rule.selector;
|
||||
});
|
||||
addBase(preflightStyles.nodes);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user