feat: add Activities component with Swiper functionality and Partners component

This commit is contained in:
2025-07-17 11:39:19 +09:00
parent 0c815c53e2
commit 02389bf051
14 changed files with 387 additions and 169 deletions
+21 -1
View File
@@ -10,7 +10,8 @@
"dependencies": { "dependencies": {
"next": "15.3.5", "next": "15.3.5",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0",
"swiper": "^11.2.10"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -5732,6 +5733,25 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swiper": {
"version": "11.2.10",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.10.tgz",
"integrity": "sha512-RMeVUUjTQH+6N3ckimK93oxz6Sn5la4aDlgPzB+rBrG/smPdCTicXyhxa+woIpopz+jewEloiEE3lKo1h9w2YQ==",
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/swiperjs"
},
{
"type": "open_collective",
"url": "http://opencollective.com/swiper"
}
],
"license": "MIT",
"engines": {
"node": ">= 4.7.0"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "4.1.11", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
+6 -5
View File
@@ -9,19 +9,20 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "15.3.5",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"next": "15.3.5" "swiper": "^11.2.10"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5", "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.3.5", "eslint-config-next": "15.3.5",
"@eslint/eslintrc": "^3" "tailwindcss": "^4",
"typescript": "^5"
} }
} }
+3
View File
@@ -0,0 +1,3 @@
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.5293 33L0 33L16.4709 16.5002L1.44248e-06 -1.39876e-06L15.5293 -7.19958e-07L32 16.5002L15.5293 33Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

+168
View File
@@ -0,0 +1,168 @@
/* eslint-disable @next/next/no-img-element */
import { fluxgore } from "@/utils/fonts";
import { useRef, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import type { Swiper as SwiperType } from "swiper";
import "swiper/css";
const iconPath = "/icons/swiper.svg";
function SwiperButton({
onClick,
direction = "next",
disabled = false,
}: {
onClick: () => void;
direction?: "next" | "prev";
disabled?: boolean;
}) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`
w-12 h-12
border border-gray-200
transition-colors duration-200
flex items-center justify-center
${direction === "prev" ? "rotate-180" : ""}
${
disabled
? "bg-[#0e5a9c] cursor-not-allowed opacity-50"
: "bg-[#0e5a9c] hover:bg-[#0c4f87] active:bg-[#0a4373] hover:shadow-md"
}
`}
aria-label={direction === "next" ? "Next slide" : "Previous slide"}
>
<img src={iconPath} alt="" className={`w-5 h-5`} />
</button>
);
}
function Slide({ title, imageSrc }: { title: string; imageSrc: string }) {
return (
<div className="flex flex-col bg-[#1068B0] py-6 px-3.5 relative">
<h2 className={`${fluxgore.className} text-4xl text-white leading-none`}>
{title}
</h2>
<img
className="w-full h-auto object-cover mt-7"
src={imageSrc}
alt="Slide Image"
/>
<img
className="absolute top-full left-0 w-full h-auto object-cover"
src="/images/activities/paper_tear.png"
alt="Background Tear"
/>
</div>
);
}
function Activities() {
const swiperRef = useRef<SwiperType | null>(null);
const [isBeginning, setIsBeginning] = useState(true);
const [isEnd, setIsEnd] = useState(false);
const handlePrevious = () => {
swiperRef.current?.slidePrev();
};
const handleNext = () => {
swiperRef.current?.slideNext();
};
const handleSwiperInit = (swiper: SwiperType) => {
swiperRef.current = swiper;
setIsBeginning(swiper.isBeginning);
setIsEnd(swiper.isEnd);
};
const handleSlideChange = (swiper: SwiperType) => {
setIsBeginning(swiper.isBeginning);
setIsEnd(swiper.isEnd);
};
return (
<div className="bg-[#F4F4F4] relative pt-20 pb-32">
<div className="container mx-auto">
<div className="flex flex-row justify-between items-center">
<h1
className={`${fluxgore.className} text-7xl text-[#060606] relative`}
>
Активности фестиваля
</h1>
<div className="flex items-center space-x-5">
<SwiperButton
onClick={handlePrevious}
direction="prev"
disabled={isBeginning}
/>
<SwiperButton
onClick={handleNext}
direction="next"
disabled={isEnd}
/>
</div>
</div>
</div>
<div className="container mx-auto overflow-visible">
<div className="mt-16 overflow-visible">
<Swiper
spaceBetween={20}
slidesPerView={3}
onSwiper={handleSwiperInit}
onSlideChange={handleSlideChange}
watchOverflow={false}
className="!overflow-visible"
breakpoints={{
640: {
slidesPerView: 1,
},
768: {
slidesPerView: 2,
},
}}
>
<SwiperSlide>
<Slide
title="Автовыставка"
imageSrc="/images/activities/car1.png"
/>
</SwiperSlide>
<SwiperSlide>
<Slide
title="Автовыставка"
imageSrc="/images/activities/car1.png"
/>
</SwiperSlide>
<SwiperSlide>
<Slide
title="Автовыставка"
imageSrc="/images/activities/car1.png"
/>
</SwiperSlide>
<SwiperSlide>
<Slide
title="Автовыставка"
imageSrc="/images/activities/car1.png"
/>
</SwiperSlide>
<SwiperSlide>
<Slide
title="Автовыставка"
imageSrc="/images/activities/car1.png"
/>
</SwiperSlide>
</Swiper>
</div>
</div>
</div>
);
}
export default Activities;
+1 -3
View File
@@ -23,8 +23,7 @@ export default function Button(props: ButtonProps) {
}; };
return ( return (
<div className={fluxgore.className}> <div className={`${fluxgore.className} relative inline-block`}>
<div className="relative inline-block">
{/* Shadow element */} {/* Shadow element */}
{shadowEnabled && ( {shadowEnabled && (
<div <div
@@ -118,6 +117,5 @@ export default function Button(props: ButtonProps) {
{props.children} {props.children}
</button> </button>
</div> </div>
</div>
); );
} }
+2 -6
View File
@@ -10,9 +10,8 @@ function CoverHeading({ children, textPosition }: CoverHeadingProps) {
const textAlign = textPosition || "left"; const textAlign = textPosition || "left";
return ( return (
<div className={fluxgore.className}>
<h1 <h1
className="text-white relative" className={`${fluxgore.className} text-white relative`}
style={{ style={{
textAlign: textAlign, textAlign: textAlign,
fontSize: "8vw", // Changed from fixed 130px to 8vw fontSize: "8vw", // Changed from fixed 130px to 8vw
@@ -62,15 +61,13 @@ function CoverHeading({ children, textPosition }: CoverHeadingProps) {
{children} {children}
</span> </span>
</h1> </h1>
</div>
); );
} }
function DateBox() { function DateBox() {
return ( return (
<div className={fluxgore.className}>
<div <div
className="bg-white text-black px-6 py-2 inline-block" className={`${fluxgore.className} bg-white text-black px-6 py-2 inline-block`}
style={{ style={{
transform: "skewX(-15deg)", transform: "skewX(-15deg)",
fontSize: "2vw", // Changed from 40px to 2.5vw fontSize: "2vw", // Changed from 40px to 2.5vw
@@ -88,7 +85,6 @@ function DateBox() {
МОСКВА 2025 МОСКВА 2025
</div> </div>
</div> </div>
</div>
); );
} }
+27 -3
View File
@@ -59,9 +59,9 @@ function Events() {
<div className="container mx-auto"> <div className="container mx-auto">
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<div className={fluxgore.className}> <h1 className={`${fluxgore.className} text-7xl text-white relative`}>
<h1 className="text-7xl text-white relative">что вас ждет</h1> что вас ждет
</div> </h1>
<p <p
className={`${gothampro.className} text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none self-end`} className={`${gothampro.className} text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none self-end`}
@@ -79,6 +79,30 @@ function Events() {
Под чутким руководством и вдохновляющим присутствием легендарного Аркадия Цареградцева, амбассадора и супер-босса соревнований, лучшие джимханисты страны покажут невероятные трюки, демонстрируя виртуозное владение машиной. Скорость, точность, дым из-под колес и филигранные маневры в ограниченном пространстве – вот что такое Джимхана." Под чутким руководством и вдохновляющим присутствием легендарного Аркадия Цареградцева, амбассадора и супер-босса соревнований, лучшие джимханисты страны покажут невероятные трюки, демонстрируя виртуозное владение машиной. Скорость, точность, дым из-под колес и филигранные маневры в ограниченном пространстве – вот что такое Джимхана."
link="#" link="#"
/> />
<EventCard
image="/events/yuka.png"
title="YUKA Drive Fest Джимхана"
description="YUKA Drive Fest Джимхана впервые врывается в Москву, и местом его дебюта станет наш Фестиваль технических видов спорта!
Это не просто гонки, это настоящий танец на асфальте, где мастерство водителя и мощь автомобиля сливаются воедино.
Под чутким руководством и вдохновляющим присутствием легендарного Аркадия Цареградцева, амбассадора и супер-босса соревнований, лучшие джимханисты страны покажут невероятные трюки, демонстрируя виртуозное владение машиной. Скорость, точность, дым из-под колес и филигранные маневры в ограниченном пространстве – вот что такое Джимхана."
link="#"
/>
<EventCard
image="/events/yuka.png"
title="YUKA Drive Fest Джимхана"
description="YUKA Drive Fest Джимхана впервые врывается в Москву, и местом его дебюта станет наш Фестиваль технических видов спорта!
Это не просто гонки, это настоящий танец на асфальте, где мастерство водителя и мощь автомобиля сливаются воедино.
Под чутким руководством и вдохновляющим присутствием легендарного Аркадия Цареградцева, амбассадора и супер-босса соревнований, лучшие джимханисты страны покажут невероятные трюки, демонстрируя виртуозное владение машиной. Скорость, точность, дым из-под колес и филигранные маневры в ограниченном пространстве – вот что такое Джимхана."
link="#"
/>
<EventCard
image="/events/yuka.png"
title="YUKA Drive Fest Джимхана"
description="YUKA Drive Fest Джимхана впервые врывается в Москву, и местом его дебюта станет наш Фестиваль технических видов спорта!
Это не просто гонки, это настоящий танец на асфальте, где мастерство водителя и мощь автомобиля сливаются воедино.
Под чутким руководством и вдохновляющим присутствием легендарного Аркадия Цареградцева, амбассадора и супер-босса соревнований, лучшие джимханисты страны покажут невероятные трюки, демонстрируя виртуозное владение машиной. Скорость, точность, дым из-под колес и филигранные маневры в ограниченном пространстве – вот что такое Джимхана."
link="#"
/>
</div> </div>
</div> </div>
</div> </div>
+5
View File
@@ -0,0 +1,5 @@
function Partners() {
return <div>Partners</div>;
}
export default Partners;
+1 -1
View File
@@ -97,7 +97,7 @@ function Scheme() {
</Button> </Button>
</div> </div>
<div className="flex flex-row mt-14 space-x-12 justify-center"> <div className="flex flex-row mt-14 space-x-12 justify-center min-h-10">
{venues.map((venue) => ( {venues.map((venue) => (
<SchemeSelect <SchemeSelect
key={venue.name} key={venue.name}
+6 -6
View File
@@ -42,18 +42,18 @@ function Video() {
<div className="container mx-auto"> <div className="container mx-auto">
<div className="flex flex-row space-x-16"> <div className="flex flex-row space-x-16">
<div className={fluxgore.className}> <h1 className={`${fluxgore.className} text-7xl text-white relative`}>
<h1 className="text-7xl text-white relative">как это Было</h1> как это Было
</div> </h1>
<div className={gothampro.className}> <p
<p className="text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none"> className={`${gothampro.className} text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none`}
>
Прошлый фестиваль технических видов спорта стал незабываемым Прошлый фестиваль технических видов спорта стал незабываемым
праздником скорости и мастерства, собрав рекордное количество праздником скорости и мастерства, собрав рекордное количество
участников и зрителей. участников и зрителей.
</p> </p>
</div> </div>
</div>
<div className="flex mt-14"> <div className="flex mt-14">
<iframe <iframe
+2
View File
@@ -1,3 +1,4 @@
import Activities from "@/components/Activities";
import Cover from "@/components/Cover"; import Cover from "@/components/Cover";
import Events from "@/components/Events"; import Events from "@/components/Events";
import Info from "@/components/Info"; import Info from "@/components/Info";
@@ -15,6 +16,7 @@ export default function Home() {
<Video /> <Video />
<Scheme /> <Scheme />
<Events /> <Events />
<Activities />
</main> </main>
</> </>
); );
+1
View File
@@ -2,6 +2,7 @@
:root { :root {
height: 100%; height: 100%;
overflow-x: hidden;
} }
body { body {