diff --git a/public/img/components/products/streaming-notebook.png b/public/img/components/products/streaming-notebook.png
index c229968..0a999ba 100644
Binary files a/public/img/components/products/streaming-notebook.png and b/public/img/components/products/streaming-notebook.png differ
diff --git a/public/videos/pages/web/hero_0.mp4 b/public/videos/pages/web/hero_0.mp4
index a1e844a..778dd1b 100644
Binary files a/public/videos/pages/web/hero_0.mp4 and b/public/videos/pages/web/hero_0.mp4 differ
diff --git a/public/videos/pages/web/hero_1.mp4 b/public/videos/pages/web/hero_1.mp4
index a170ec9..9579a85 100644
Binary files a/public/videos/pages/web/hero_1.mp4 and b/public/videos/pages/web/hero_1.mp4 differ
diff --git a/public/videos/pages/web/hero_2.mp4 b/public/videos/pages/web/hero_2.mp4
index d651a20..8e19fab 100644
Binary files a/public/videos/pages/web/hero_2.mp4 and b/public/videos/pages/web/hero_2.mp4 differ
diff --git a/public/videos/pages/web/hero_3.mp4 b/public/videos/pages/web/hero_3.mp4
index 5b5c43f..68bbcb1 100644
Binary files a/public/videos/pages/web/hero_3.mp4 and b/public/videos/pages/web/hero_3.mp4 differ
diff --git a/public/videos/pages/web/hero_4.mp4 b/public/videos/pages/web/hero_4.mp4
index cd35dd1..a387e62 100644
Binary files a/public/videos/pages/web/hero_4.mp4 and b/public/videos/pages/web/hero_4.mp4 differ
diff --git a/public/videos/pages/web/hero_5.mp4 b/public/videos/pages/web/hero_5.mp4
index 1ef2dc6..6cebbff 100644
Binary files a/public/videos/pages/web/hero_5.mp4 and b/public/videos/pages/web/hero_5.mp4 differ
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index cce44a6..5629c9a 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -12,7 +12,7 @@ export default function MainLayout({ children }: PropsWithChildren) {
return (
-
+
{children}
diff --git a/src/app/(main)/page.tsx b/src/app/(main)/page.tsx
index df1cc6b..44a9103 100644
--- a/src/app/(main)/page.tsx
+++ b/src/app/(main)/page.tsx
@@ -15,7 +15,6 @@ import { queryCompaniesOptions } from "@/queries/getCompanies";
import { queryCompaniesCountOptions } from "@/queries/getCompaniesCount";
import NewStreaming from "@/components/pages/MainPage/NewStreaming";
import NewMap from "@/components/pages/MainPage/Map/NewMap";
-import WebMain from "@/components/pages/WebPage/WebMain";
export default async function HomePage() {
const queryClient = new QueryClient();
@@ -55,20 +54,19 @@ export default async function HomePage() {
return (
- {/*
+
- */}
+
{/* */}
{/* */}
{/* */}
- {/*
- */}
+
+
{/* */}
- {/* */}
-
+
);
}
diff --git a/src/components/pages/WebPage/PageBlocks/WebHero.tsx b/src/components/pages/WebPage/PageBlocks/WebHero.tsx
index 1d28298..cfb9ed1 100644
--- a/src/components/pages/WebPage/PageBlocks/WebHero.tsx
+++ b/src/components/pages/WebPage/PageBlocks/WebHero.tsx
@@ -1,49 +1,79 @@
/* eslint-disable @next/next/no-img-element */
"use client";
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
import SearchIcon from "@/icons/SearchIcon";
import GenPlanSearchIcon from "@/components/icons/GenPlanSearchIcon";
import TreeIcon from "@/components/icons/TreeIcon";
import FavoriteIcon from "@/components/icons/FavoriteIcon";
import WebHeroControlBtn from "../PageComponents/WebHeroControlBtn";
-import { AnimatePresence, motion } from "framer-motion";
import ThreeDTourIcon from "@/components/icons/3DTourIcon";
import InfoIcon from "@/components/icons/InfoIcon";
+const VIDEO_COUNT = 6;
+type VIDEO_EVENT = React.SyntheticEvent;
+
export default function WebHero() {
- const videoRef = useRef(null);
- const [currentVideo, setCurrentVideo] = useState(0);
+ const [activeVideoElement, setActiveVideoElement] = useState<0 | 1>(0);
const [playbackProgress, setPlaybackProgress] = useState(0);
- const [selectedVideo, setSelectedVideo] = useState(null);
+ const [currentVideo, setCurrentVideo] = useState(0); // 0 | 1 | 2 | 3 | 4 | 5
+ const videoElements = useRef<(HTMLVideoElement | null)[]>([null, null]);
+ const isInitialLoad = useRef(true);
- const updateProgress = useCallback(() => {
- if (!videoRef.current) return;
- const { currentTime, duration } = videoRef.current;
- const progressPercent = duration ? (currentTime / duration) * 100 : 0;
+ function handleTimeUpdate(e: VIDEO_EVENT) {
+ if (e.currentTarget !== videoElements.current[activeVideoElement]) return;
- setPlaybackProgress(progressPercent);
- }, [currentVideo]);
+ const { currentTime, duration } = e.currentTarget;
+ const progressPercentage = duration ? (currentTime / duration) * 100 : 0;
+ setPlaybackProgress(progressPercentage);
+ }
- const handleTimeUpdate = useCallback(() => {
- updateProgress();
- }, [updateProgress]);
-
- useEffect(() => {
- setPlaybackProgress(0);
- }, [currentVideo]);
-
- const onVideoEnd = () => {
- setCurrentVideo((cur) => (cur + 1) % 6);
- };
+ function handleVideoEnd(e: VIDEO_EVENT) {
+ if (e.currentTarget !== videoElements.current[activeVideoElement]) return;
+ setCurrentVideo((cur) => (cur + 1) % VIDEO_COUNT);
+ }
function onControlButtonClick(video: number) {
- if (selectedVideo === video) setSelectedVideo(null);
+ if (currentVideo === video) return;
else {
setCurrentVideo(video);
- setSelectedVideo(video);
+ setPlaybackProgress(0);
}
}
+ // Как только произошло переключение на новое видео (изменился currentVideo)
+ useEffect(() => {
+ // indexOfVideoToPlay = 0 || 1, используется для определения индекса видео, которое нужно загрузить. При первой загрузке index = 0
+ const indexOfVideoToPlay = isInitialLoad.current
+ ? 0
+ : ((1 - activeVideoElement) as 0 | 1);
+ isInitialLoad.current = false;
+
+ // Получаем видео, которое будет проиграно
+ const videoToPlay = videoElements.current[indexOfVideoToPlay];
+ if (!videoToPlay) return;
+
+ const handleCanPlay = () => {
+ // Как только можем проиграть текущее видео, останавливаем то, которое было до него
+ const currentlyPlayingVideo =
+ videoElements.current[1 - indexOfVideoToPlay];
+ currentlyPlayingVideo?.pause();
+
+ // Запускаем текущее видео
+ videoToPlay.currentTime = 0;
+ videoToPlay.play().catch(() => {});
+ setActiveVideoElement(indexOfVideoToPlay);
+ };
+
+ videoToPlay.src = `/videos/pages/web/hero_${currentVideo}.mp4`;
+ videoToPlay.addEventListener("canplaythrough", handleCanPlay, {
+ once: true,
+ });
+
+ return () => {
+ videoToPlay.removeEventListener("canplaythrough", handleCanPlay);
+ };
+ }, [currentVideo]);
+
return (
-
- setPlaybackProgress(0)}
- playsInline
- muted
- autoPlay
- src={`/videos/pages/web/hero_${currentVideo}.mp4`}
- className="absolute w-[50.694vw] aspect-[730/472] top-[4vw] left-[6.458vw]
- md:max-lg:w-[87.2%] max-lg:md:aspect-[585/345] md:max-lg:top-[1.5vw] md:max-lg:left-[6.458vw]
- max-md:w-[80.2%] max-md:left-[9.444vw] max-md:top-[1.444vw]"
- />
-
+ {/* Два видео для избежания мерцания при смене src */}
+
(videoElements.current[0] = el)}
+ active={activeVideoElement === 0}
+ onEnded={handleVideoEnd}
+ onTimeUpdate={handleTimeUpdate}
+ onLoadStart={() => setPlaybackProgress(0)}
+ />
+ (videoElements.current[1] = el)}
+ active={activeVideoElement === 1}
+ onEnded={handleVideoEnd}
+ onTimeUpdate={handleTimeUpdate}
+ onLoadStart={() => setPlaybackProgress(0)}
+ />
+
}
+ title={"Search on the Master Plan"}
+ icon={}
className="top-[14.722vw] left-0"
active={currentVideo === 0}
callback={() => onControlButtonClick(0)}
progress={currentVideo === 0 ? playbackProgress : 0}
/>
}
+ title={"Search"}
+ icon={}
className="top-[20.486vw] left-[8.194vw]"
active={currentVideo === 1}
callback={() => onControlButtonClick(1)}
@@ -145,3 +172,32 @@ export default function WebHero() {
);
}
+
+interface WebHeroVideoProps
+ extends React.VideoHTMLAttributes
{
+ refCallback: (el: HTMLVideoElement) => void;
+ active: boolean;
+}
+
+function WebHeroVideo({ refCallback, active, ...props }: WebHeroVideoProps) {
+ return (
+