This commit is contained in:
2026-01-14 10:53:32 +05:00
parent 52b202e631
commit 9a26117e1c
8 changed files with 118 additions and 138 deletions
+9 -31
View File
@@ -56,38 +56,16 @@ export default function RootLayout({
</LenisProvider>
</QueryProvider>
<Script id="metrika-counter" strategy="afterInteractive">
{`(function (m, e, t, r, i, k, a) {
m[i] =
m[i] ||
function () {
(m[i].a = m[i].a || []).push(arguments);
};
m[i].l = 1 * new Date();
for (var j = 0; j < document.scripts.length; j++) {
if (document.scripts[j].src === r) {
return;
}
}
(k = e.createElement(t)),
(a = e.getElementsByTagName(t)[0]),
(k.async = 1),
(k.src = r),
a.parentNode.insertBefore(k, a);
})(
window,
document,
'script',
'https://mc.yandex.ru/metrika/tag.js',
'ym',
);
ym(98587267, 'init', {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true,
});`}
{` (function(m,e,t,r,i,k,a){
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=105144448', 'ym');
ym(105144448, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});`}
</Script>
<noscript><div><img src="https://mc.yandex.ru/watch/105144448" style={{position:"absolute", left: "-9999px"}} alt="" /></div></noscript>
</body>
</html>
);
@@ -9,7 +9,7 @@ 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 { useEffect, useMemo, useRef, useState } from "react";
import { GridContextProvider, GridDropZone, swap } from "react-grid-dnd";
import AddIcon from "@/components/icons/AddIcon";
import RestartIcon from "@/components/icons/RestartIcon";
@@ -26,32 +26,36 @@ export function Clients() {
const [shuffled, setShuffled] = useState<ICompany[]>([]);
const excludeCompanies = [
"Рисан",
"Efes",
"ЭНКО",
"КамаСтройИнвест",
"Брусника",
"Маяк",
"Мавис",
"НКС",
"Паритет",
ето",
"СБК",
"Делом",
"УГМК",
"ГК Альфа",
"Синара",
"Кортрос",
"Атмосфера",
"Голос",
"Сибинтел",
"Основа",
"Центр-Инвест",
"DNS",
"ПСК Дом девелопмент",
ИК",
];
const excludeCompanies = useMemo(
() => [
"Рисан",
"Efes",
"ЭНКО",
"КамаСтройИнвест",
"Брусника",
"Маяк",
"Мавис",
"НКС",
"Паритет",
"Лето",
"СБК",
"Делом",
"УГМК",
"ГК Альфа",
"Синара",
"Кортрос",
"Атмосфера",
"Голос",
"Сибинтел",
"Основа",
"Центр-Инвест",
"DNS",
СК Дом девелопмент",
"ПИК",
"ГК «‎Сумма элементов»",
],
[]
);
useEffect(() => {
if (!companies) return;
@@ -63,7 +67,7 @@ export function Clients() {
setEnCompanies(
shuffled.filter((company) => !excludeCompanies.includes(company.title))
);
}, [shuffled]);
}, [excludeCompanies, shuffled]);
const ref = useRef<HTMLDivElement>(null);
@@ -108,7 +112,7 @@ export function Clients() {
}`}
ref={ref}
>
<div className="flex items-center justify-between">
<div className="flex justify-between items-center">
<Title className="mx-auto">
<span className="text-gradient">
{count !== undefined && getCompaniesCount(count, locale)}
@@ -117,7 +121,7 @@ export function Clients() {
</Title>
<OpenFormModalWrapper
modal={<CompanyFormModal action="create" />}
className="aspect-square flex flex-col items-center justify-center gap-3"
className="aspect-square flex flex-col gap-3 justify-center items-center"
>
<GradientButton>
<div className="text-white lg:size-[2.778vw] size-7">
@@ -157,7 +161,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={`z-[1] lg:w-[calc((100vw-120px)/11)] md:max-lg:w-[calc((100vw-64px)/5)] w-[calc((100vw-36px)/3)] aspect-square flex absolute flex-col gap-3 justify-center items-center`}
style={{
left:
(enCompanies.length % (isLg ? 11 : isMd ? 5 : 3)) *
@@ -41,7 +41,7 @@ function NewStreaming() {
</div>
</Link>
</div>
<div className="max-lg:h-[400px]">
<div className="max-lg:h-[400px] flex-1">
<VideoPlayer src={"/videos/pages/home/streaming.mp4"} showMutingBtn>
<p className="accent font-medium absolute top-6 left-6 w-[39.861vw] max-lg:w-[90vw] max-lg:text-[20px] ">
Graff.estate stream is available on any device all you need for
+2 -2
View File
@@ -11,8 +11,8 @@ import { queryOptions, useQuery } from "@tanstack/react-query";
import { useLocale, useTranslations } from "next-intl";
export function Projects() {
// const { data: projects } = useGetProjectsQuery();
const { data: projects } = useQuery(queryProjectsOptions);
const { data: projects } = useGetProjectsQuery();
// const { data: projects } = useQuery(queryProjectsOptions);
const t = useTranslations("projects");
const locale = useLocale();
@@ -1,18 +1,18 @@
'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 { useTranslations } from 'next-intl';
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 { useTranslations } from "next-intl";
export function Streaming() {
const { data: streamingProjects } = useGetProjectsQuery(
'Удаленная демонстрация'
"Удаленная демонстрация"
);
const [isViewportEntered, setIsViewportEntered] = useState(false);
@@ -41,28 +41,27 @@ export function Streaming() {
touchEventOptions: { passive: false },
});
const t = useTranslations('streaming');
const t = useTranslations("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">
{t('title1')}
<span className="text-gradient"> {t('title2')}</span>{' '}
{t('title3')}
{t("title1")}
<span className="text-gradient"> {t("title2")}</span> {t("title3")}
</Title>
<Title headerLevel={2} className="md:hidden text-center select-none">
<span className="text-gradient">{t('title4')}</span>
{t('title5')}
<span className="text-gradient">{t("title4")}</span>
{t("title5")}
</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:max-lg:flex grid-cols-3 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"
{...handlers}
>
{streamingProjects?.slice(0, 3).map((project, index, { length }) => (
{/* {streamingProjects?.slice(0, 3).map((project, index, { length }) => (
<StreamingProject
key={project.id}
{...project}
@@ -71,39 +70,37 @@ export function Streaming() {
count={length + 1}
href="/"
/>
))}
<div
))} */}
{/* <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)] ${
streamingProjects &&
(Math.min(streamingProjects!.length + 1, 4) - 1 === current
? 'max-md:translate-z-10'
: 'max-md:scale-85')
? "max-md:translate-z-10"
: "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">
<div className="flex flex-col items-center space-y-6">
<p className="heading2 font-medium text-center">
{t('info')}
</p>
<p className="heading2 font-medium text-center">{t("info")}</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"
>
{t('leave_request')}
{t("leave_request")}
</Link>
</div>
</div>
</div>
</div> */}
</div>
<VideoPlayer
src="/videos/pages/home/streaming.mp4"
@@ -111,10 +108,10 @@ export function Streaming() {
loop
autoPlay
muted
className="lg:apect-[1400/640] max-h-dvh md:max-lg:aspect-[736/480] aspect-[340/600]"
className="lg:apect-[1400/640]a max-h-dvh md:max-lg:aspect-[736/480] aspect-[340/600] flex-1"
>
<p className="absolute font-medium md:bottom-6 md:left-6 bottom-4 left-4 lg:max-w-[40%] md:max-lg:max-w-[80%] accent">
{t('demo_available')}
{t("demo_available")}
</p>
</VideoPlayer>
</motion.div>
+1 -1
View File
@@ -125,7 +125,7 @@ export const enMapProjects: EnMapProject[] = [
video: "/videos/pages/home/Риваят.mp4",
},
{
logo: "/img/pages/home/Brusnika.png",
logo: "/img/pages/home/brusnika.png",
video: "/videos/pages/home/Izdanie.mp4",
},
];
+24 -23
View File
@@ -1,34 +1,35 @@
import { api } from '@/api';
import { streaming } from '@/consts/streaming';
import { IProject } from '@/types/IProject';
import { queryOptions, useQuery } from '@tanstack/react-query';
import { api } from "@/api";
import { streaming } from "@/consts/streaming";
import { IProject } from "@/types/IProject";
import { queryOptions, useQuery } from "@tanstack/react-query";
export const queryProjectsOptions = queryOptions({
queryKey: ['projects'],
queryFn: () => api.get('projects').json<IProject[]>(),
queryKey: ["projects"],
queryFn: () => api.get("projects").json<IProject[]>(),
select: (data) => data.filter((p) => "BARAHA TOWN" !== p.englishTitle),
});
export function useGetProjectsQuery(tags?: string | string[]) {
return useQuery(
tags && tags.length > 0
? queryOptions({
queryKey: ['projects', ...(Array.isArray(tags) ? tags : [tags])],
queryFn: () =>
api
.get(
`projects?${Array.isArray(tags)
? tags.map((tag) => `tags=${tag}`).join('&')
: 'tags=' + tags
}`
)
.json<IProject[]>(),
select:
tags === 'Удаленная демонстрация'
? (data) =>
data.filter((p) => streaming.some((s) => s.title === p.title))
: undefined,
})
queryKey: ["projects", ...(Array.isArray(tags) ? tags : [tags])],
queryFn: () =>
api
.get(
`projects?${
Array.isArray(tags)
? tags.map((tag) => `tags=${tag}`).join("&")
: "tags=" + tags
}`
)
.json<IProject[]>(),
select:
tags === "Удаленная демонстрация"
? (data) =>
data.filter((p) => streaming.some((s) => s.title === p.title))
: (data) => data.filter((p) => "BARAHA TOWN" !== p.englishTitle),
})
: queryProjectsOptions
);
}
+14 -14
View File
@@ -1,6 +1,6 @@
'use client';
"use client";
import { AnimatePresence, motion } from 'framer-motion';
import { AnimatePresence, motion } from "framer-motion";
import {
ComponentProps,
forwardRef,
@@ -8,9 +8,9 @@ import {
useImperativeHandle,
useRef,
useState,
} from 'react';
import { VideoMutingBtn } from './VideoMutingBtn';
import { VideoProgressBar } from './VideoProgressBar';
} from "react";
import { VideoMutingBtn } from "./VideoMutingBtn";
import { VideoProgressBar } from "./VideoProgressBar";
export const VideoPlayer = forwardRef<
HTMLVideoElement,
@@ -18,7 +18,7 @@ export const VideoPlayer = forwardRef<
src: string;
showMutingBtn: boolean;
children?: React.ReactNode;
} & ComponentProps<'video'>
} & ComponentProps<"video">
>(
(
{ src, showMutingBtn, children, loop = true, autoPlay = true, className },
@@ -48,7 +48,7 @@ export const VideoPlayer = forwardRef<
function handlePlaybackClick() {
if (!videoRef.current) return;
setPlaying(videoRef.current.paused);
videoRef.current[videoRef.current.paused ? 'play' : 'pause']();
videoRef.current[videoRef.current.paused ? "play" : "pause"]();
}
useEffect(() => {
@@ -57,8 +57,8 @@ export const VideoPlayer = forwardRef<
const timeUpdateHandler = () =>
setProgress(((video.currentTime ?? 0) / (video.duration ?? 1)) * 100);
videoRef.current.addEventListener('timeupdate', timeUpdateHandler);
return () => video.removeEventListener('timeupdate', timeUpdateHandler);
videoRef.current.addEventListener("timeupdate", timeUpdateHandler);
return () => video.removeEventListener("timeupdate", timeUpdateHandler);
}, []);
const [isViewportEntered, setIsViewportEntered] = useState(false);
@@ -71,19 +71,19 @@ export const VideoPlayer = forwardRef<
return (
<motion.div
viewport={{ margin: '-10% 0% 0% 0%', once: true }}
viewport={{ margin: "-10% 0% 0% 0%", once: true }}
onViewportEnter={handleOnViewportFeatureEnter}
className="relative h-full"
className="relative w-full h-full"
>
<video
ref={videoRef}
src={isViewportEntered ? src : ''}
src={isViewportEntered ? src : ""}
autoPlay={autoPlay}
muted={muted}
loop={loop}
playsInline
className={`lg:rounded-[1.111vw] rounded-2xl w-full h-full object-cover${
className ? ' ' + className : ''
className ? " " + className : ""
}`}
/>
{showMutingBtn && (
@@ -117,4 +117,4 @@ export const VideoPlayer = forwardRef<
}
);
VideoPlayer.displayName = 'VideoPlayer';
VideoPlayer.displayName = "VideoPlayer";