feat: add Activities component with Swiper functionality and Partners component
This commit is contained in:
@@ -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;
|
||||
+85
-87
@@ -23,101 +23,99 @@ export default function Button(props: ButtonProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={fluxgore.className}>
|
||||
<div className="relative inline-block">
|
||||
{/* Shadow element */}
|
||||
{shadowEnabled && (
|
||||
<div
|
||||
className="absolute bg-black transition-all duration-150 ease-in-out"
|
||||
style={{
|
||||
top: "8px",
|
||||
left: "8px",
|
||||
right: "-8px",
|
||||
bottom: "-8px",
|
||||
clipPath:
|
||||
"polygon(20px 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%, 0 20px)",
|
||||
zIndex: 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Button */}
|
||||
<button
|
||||
className={`${getButtonStyles()} inline-block relative transition-all duration-150 ease-in-out ${
|
||||
shadowEnabled
|
||||
? "active:translate-x-1 active:translate-y-1"
|
||||
: "hover:scale-105 active:scale-95"
|
||||
} ${props.className}`}
|
||||
onClick={props.onClick}
|
||||
<div className={`${fluxgore.className} relative inline-block`}>
|
||||
{/* Shadow element */}
|
||||
{shadowEnabled && (
|
||||
<div
|
||||
className="absolute bg-black transition-all duration-150 ease-in-out"
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
lineHeight: "1.2",
|
||||
padding: "20px 40px",
|
||||
minWidth: "280px",
|
||||
top: "8px",
|
||||
left: "8px",
|
||||
right: "-8px",
|
||||
bottom: "-8px",
|
||||
clipPath:
|
||||
"polygon(20px 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%, 0 20px)",
|
||||
border: "none",
|
||||
zIndex: 1,
|
||||
zIndex: 0,
|
||||
}}
|
||||
onMouseEnter={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "6px";
|
||||
shadow.style.left = "6px";
|
||||
shadow.style.right = "-6px";
|
||||
shadow.style.bottom = "-6px";
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* Button */}
|
||||
<button
|
||||
className={`${getButtonStyles()} inline-block relative transition-all duration-150 ease-in-out ${
|
||||
shadowEnabled
|
||||
? "active:translate-x-1 active:translate-y-1"
|
||||
: "hover:scale-105 active:scale-95"
|
||||
} ${props.className}`}
|
||||
onClick={props.onClick}
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
lineHeight: "1.2",
|
||||
padding: "20px 40px",
|
||||
minWidth: "280px",
|
||||
clipPath:
|
||||
"polygon(20px 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%, 0 20px)",
|
||||
border: "none",
|
||||
zIndex: 1,
|
||||
}}
|
||||
onMouseEnter={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "6px";
|
||||
shadow.style.left = "6px";
|
||||
shadow.style.right = "-6px";
|
||||
shadow.style.bottom = "-6px";
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseLeave={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "8px";
|
||||
shadow.style.left = "8px";
|
||||
shadow.style.right = "-8px";
|
||||
shadow.style.bottom = "-8px";
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseLeave={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "8px";
|
||||
shadow.style.left = "8px";
|
||||
shadow.style.right = "-8px";
|
||||
shadow.style.bottom = "-8px";
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseDown={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "4px";
|
||||
shadow.style.left = "4px";
|
||||
shadow.style.right = "-4px";
|
||||
shadow.style.bottom = "-4px";
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseDown={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "4px";
|
||||
shadow.style.left = "4px";
|
||||
shadow.style.right = "-4px";
|
||||
shadow.style.bottom = "-4px";
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseUp={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "6px";
|
||||
shadow.style.left = "6px";
|
||||
shadow.style.right = "-6px";
|
||||
shadow.style.bottom = "-6px";
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseUp={
|
||||
shadowEnabled
|
||||
? (e) => {
|
||||
const shadow = e.currentTarget
|
||||
.previousElementSibling as HTMLElement;
|
||||
if (shadow) {
|
||||
shadow.style.top = "6px";
|
||||
shadow.style.left = "6px";
|
||||
shadow.style.right = "-6px";
|
||||
shadow.style.bottom = "-6px";
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+58
-62
@@ -10,83 +10,79 @@ function CoverHeading({ children, textPosition }: CoverHeadingProps) {
|
||||
const textAlign = textPosition || "left";
|
||||
|
||||
return (
|
||||
<div className={fluxgore.className}>
|
||||
<h1
|
||||
className="text-white relative"
|
||||
style={{
|
||||
textAlign: textAlign,
|
||||
fontSize: "8vw", // Changed from fixed 130px to 8vw
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
{/* Shadow layer */}
|
||||
{textAlign === "left" && (
|
||||
<span
|
||||
className="absolute top-0 text-black"
|
||||
style={{
|
||||
transform: `translate(1.1vw, 0.7vw)`, // Changed from fixed pixels to vw
|
||||
textAlign: textAlign,
|
||||
zIndex: 1,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{textAlign === "right" && (
|
||||
<span
|
||||
className="absolute top-0 text-black"
|
||||
style={{
|
||||
transform: `translate(calc(-100% + 1.1vw), 0.7vw)`, // Changed from fixed pixels to vw
|
||||
textAlign: textAlign,
|
||||
zIndex: 1,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Main text */}
|
||||
<h1
|
||||
className={`${fluxgore.className} text-white relative`}
|
||||
style={{
|
||||
textAlign: textAlign,
|
||||
fontSize: "8vw", // Changed from fixed 130px to 8vw
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
{/* Shadow layer */}
|
||||
{textAlign === "left" && (
|
||||
<span
|
||||
className="relative text-white"
|
||||
className="absolute top-0 text-black"
|
||||
style={{
|
||||
zIndex: 3,
|
||||
display: "block",
|
||||
transform: `translate(1.1vw, 0.7vw)`, // Changed from fixed pixels to vw
|
||||
textAlign: textAlign,
|
||||
WebkitTextStroke: "0.75vw black", // Changed from fixed 12px to 0.75vw
|
||||
paintOrder: "stroke fill",
|
||||
zIndex: 1,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{textAlign === "right" && (
|
||||
<span
|
||||
className="absolute top-0 text-black"
|
||||
style={{
|
||||
transform: `translate(calc(-100% + 1.1vw), 0.7vw)`, // Changed from fixed pixels to vw
|
||||
textAlign: textAlign,
|
||||
zIndex: 1,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Main text */}
|
||||
<span
|
||||
className="relative text-white"
|
||||
style={{
|
||||
zIndex: 3,
|
||||
display: "block",
|
||||
textAlign: textAlign,
|
||||
WebkitTextStroke: "0.75vw black", // Changed from fixed 12px to 0.75vw
|
||||
paintOrder: "stroke fill",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
function DateBox() {
|
||||
return (
|
||||
<div className={fluxgore.className}>
|
||||
<div
|
||||
className="bg-white text-black px-6 py-2 inline-block"
|
||||
style={{
|
||||
transform: "skewX(-15deg)",
|
||||
fontSize: "2vw", // Changed from 40px to 2.5vw
|
||||
lineHeight: "1.2",
|
||||
filter: `
|
||||
<div
|
||||
className={`${fluxgore.className} bg-white text-black px-6 py-2 inline-block`}
|
||||
style={{
|
||||
transform: "skewX(-15deg)",
|
||||
fontSize: "2vw", // Changed from 40px to 2.5vw
|
||||
lineHeight: "1.2",
|
||||
filter: `
|
||||
drop-shadow(8px 8px 0px black)
|
||||
drop-shadow(-2px -2px 0px rgba(0,0,0,0.3))
|
||||
`,
|
||||
border: "4px solid black",
|
||||
}}
|
||||
>
|
||||
<div style={{ transform: "skewX(15deg)" }}>
|
||||
5-7 СЕНТЯБРЯ
|
||||
<br />
|
||||
МОСКВА 2025
|
||||
</div>
|
||||
border: "4px solid black",
|
||||
}}
|
||||
>
|
||||
<div style={{ transform: "skewX(15deg)" }}>
|
||||
5-7 СЕНТЯБРЯ
|
||||
<br />
|
||||
МОСКВА 2025
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -59,9 +59,9 @@ function Events() {
|
||||
|
||||
<div className="container mx-auto">
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className={fluxgore.className}>
|
||||
<h1 className="text-7xl text-white relative">что вас ждет</h1>
|
||||
</div>
|
||||
<h1 className={`${fluxgore.className} text-7xl text-white relative`}>
|
||||
что вас ждет
|
||||
</h1>
|
||||
|
||||
<p
|
||||
className={`${gothampro.className} text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none self-end`}
|
||||
@@ -79,6 +79,30 @@ function Events() {
|
||||
Под чутким руководством и вдохновляющим присутствием легендарного Аркадия Цареградцева, амбассадора и супер-босса соревнований, лучшие джимханисты страны покажут невероятные трюки, демонстрируя виртуозное владение машиной. Скорость, точность, дым из-под колес и филигранные маневры в ограниченном пространстве – вот что такое Джимхана."
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
function Partners() {
|
||||
return <div>Partners</div>;
|
||||
}
|
||||
|
||||
export default Partners;
|
||||
@@ -97,7 +97,7 @@ function Scheme() {
|
||||
</Button>
|
||||
</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) => (
|
||||
<SchemeSelect
|
||||
key={venue.name}
|
||||
|
||||
+10
-10
@@ -42,17 +42,17 @@ function Video() {
|
||||
|
||||
<div className="container mx-auto">
|
||||
<div className="flex flex-row space-x-16">
|
||||
<div className={fluxgore.className}>
|
||||
<h1 className="text-7xl text-white relative">как это Было</h1>
|
||||
</div>
|
||||
<h1 className={`${fluxgore.className} text-7xl text-white relative`}>
|
||||
как это Было
|
||||
</h1>
|
||||
|
||||
<div className={gothampro.className}>
|
||||
<p className="text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none">
|
||||
Прошлый фестиваль технических видов спорта стал незабываемым
|
||||
праздником скорости и мастерства, собрав рекордное количество
|
||||
участников и зрителей.
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className={`${gothampro.className} text-[#E6E6E6] opacity-90 text-xl max-w-[536px] leading-none`}
|
||||
>
|
||||
Прошлый фестиваль технических видов спорта стал незабываемым
|
||||
праздником скорости и мастерства, собрав рекордное количество
|
||||
участников и зрителей.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex mt-14">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Activities from "@/components/Activities";
|
||||
import Cover from "@/components/Cover";
|
||||
import Events from "@/components/Events";
|
||||
import Info from "@/components/Info";
|
||||
@@ -15,6 +16,7 @@ export default function Home() {
|
||||
<Video />
|
||||
<Scheme />
|
||||
<Events />
|
||||
<Activities />
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
:root {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
Reference in New Issue
Block a user