Carusel changes to previous ver, sponsors L to R

This commit is contained in:
2026-05-02 21:51:59 +03:00
parent 4369102a75
commit aeb8f52682
4 changed files with 163 additions and 138 deletions

View File

@@ -74,6 +74,10 @@ const Header = ({ navItems }: HeaderProps) => {
}; };
}, []); }, []);
useEffect(() => {
setIsMobileMenuOpen(false);
}, [pathname]);
return ( return (
<header className="px-4 py-2 md:px-8 flex items-center bg-[#0E0F19] border-b-3 border-[#1F5673] justify-between"> <header className="px-4 py-2 md:px-8 flex items-center bg-[#0E0F19] border-b-3 border-[#1F5673] justify-between">
{/* Logo */} {/* Logo */}
@@ -90,7 +94,7 @@ const Header = ({ navItems }: HeaderProps) => {
{/* Right side - Navigation + controls */} {/* Right side - Navigation + controls */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Desktop Navigation */} {/* Desktop Navigation */}
<nav className="hidden xl:flex items-center gap-3"> <nav className="hidden lg:flex items-center gap-3">
{mainNavItems.map((item) => { {mainNavItems.map((item) => {
const isActive = pathname === item.href; const isActive = pathname === item.href;
const isDisabled = disabledNavHrefs.has(item.href); const isDisabled = disabledNavHrefs.has(item.href);
@@ -110,20 +114,7 @@ const Header = ({ navItems }: HeaderProps) => {
: "hover:bg-[#00A3E0]/20" : "hover:bg-[#00A3E0]/20"
}`} }`}
> >
<span className="flex items-center gap-2"> {item.label}
<span>{item.label}</span>
{navIconByHref[item.href] ? (
<span
className={`material-symbols-outlined text-[1.4rem]! leading-none ${
isActive
? "text-black"
: "text-[#00A3E0] group-hover:text-[#EEE5E5]"
}`}
>
{navIconByHref[item.href]}
</span>
) : null}
</span>
</Link> </Link>
); );
})} })}
@@ -139,7 +130,7 @@ const Header = ({ navItems }: HeaderProps) => {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="xl:hidden size-10 cursor-pointer" className="lg:hidden size-10 cursor-pointer"
> >
<MdMenu className="size-10 text-[#EEE5E5]" /> <MdMenu className="size-10 text-[#EEE5E5]" />
<span className="sr-only">Menu</span> <span className="sr-only">Menu</span>
@@ -147,7 +138,7 @@ const Header = ({ navItems }: HeaderProps) => {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
align="end" align="end"
className="xl:hidden w-64 translate-y-4 rounded-none border-3 border-[#1F5673] bg-[#0E0F19] p-0" className="lg:hidden w-64 translate-y-4 rounded-none border-3 border-[#1F5673] bg-[#0E0F19] p-0"
> >
{mainNavItems.map((item, index) => { {mainNavItems.map((item, index) => {
const isActive = pathname === item.href; const isActive = pathname === item.href;

View File

@@ -1,6 +1,5 @@
"use client"; "use client";
import { useState, useEffect, useCallback } from "react";
import { vipnagorgialla } from "@/components/Vipnagorgialla"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
@@ -83,38 +82,22 @@ const sponsors: Sponsor[] = [
}, },
]; ];
// Split sponsors into slides (6 per slide)
const SPONSORS_PER_SLIDE = 6;
const slides: Sponsor[][] = [];
for (let i = 0; i < sponsors.length; i += SPONSORS_PER_SLIDE) {
slides.push(sponsors.slice(i, i + SPONSORS_PER_SLIDE));
}
interface SponsorsProps { interface SponsorsProps {
showTitle?: boolean; showTitle?: boolean;
className?: string; className?: string;
} }
const tickerSponsors = [...sponsors, ...sponsors, ...sponsors, ...sponsors];
export default function Sponsors({ export default function Sponsors({
showTitle = true, showTitle = true,
className = "", className = "",
}: SponsorsProps) { }: SponsorsProps) {
const t = useTranslations(); const t = useTranslations();
const [current, setCurrent] = useState(0);
const next = useCallback(
() => setCurrent((c) => (c + 1) % slides.length),
[],
);
useEffect(() => {
const id = setInterval(next, 5000);
return () => clearInterval(id);
}, [next]);
return ( return (
<div <div
className={`flex flex-col max-w-[1920px] xl:h-[414px] mx-auto ${vipnagorgialla.className} font-bold italic border-[#1F5673] ${className}`} className={`flex flex-col w-full xl:h-[414px] mx-auto ${vipnagorgialla.className} font-bold italic border-[#1F5673] ${className}`}
> >
{showTitle && ( {showTitle && (
<h3 className="text-4xl md:text-5xl dark:text-[#EEE5E5] text-[#2A2C3F] px-12 pt-8 pb-4"> <h3 className="text-4xl md:text-5xl dark:text-[#EEE5E5] text-[#2A2C3F] px-12 pt-8 pb-4">
@@ -122,53 +105,44 @@ export default function Sponsors({
</h3> </h3>
)} )}
{/* Carousel container */} <div className="relative xl:flex-1 overflow-hidden py-8 sm:py-10 xl:py-4 2xl:py-6">
<div className="relative xl:flex-1 overflow-x-hidden overflow-y-visible"> <div className="ticker-track flex items-center w-max gap-8 sm:gap-10 md:gap-12 xl:gap-16 2xl:gap-20 px-8 sm:px-10 xl:px-14 2xl:px-20">
<div {tickerSponsors.map((sponsor, index) => (
className="flex h-full transition-transform duration-500 ease-in-out" <NextLink
style={{ transform: `translateX(-${current * 100}%)` }} key={`${sponsor.alt}-${index}`}
> href={sponsor.href}
{slides.map((slideSponsors, slideIndex) => ( target="_blank"
<div className="flex items-center justify-center shrink-0"
key={slideIndex} aria-hidden={index >= sponsors.length}
className="flex-none w-full xl:h-full grid grid-cols-2 sm:grid-cols-3 place-items-center gap-6 px-6 py-6 xl:flex xl:items-center xl:justify-center xl:gap-12 xl:px-12 xl:py-0" tabIndex={index >= sponsors.length ? -1 : undefined}
> >
{slideSponsors.map((sponsor, i) => ( <Image
<NextLink src={sponsor.src}
key={i} alt={sponsor.alt}
href={sponsor.href} width={sponsor.width}
target="_blank" height={sponsor.height}
className="flex items-center justify-center w-full" className={`object-contain max-h-[80px] max-w-[120px] sm:max-h-[110px] sm:max-w-[150px] md:max-h-[130px] md:max-w-[180px] lg:max-h-[140px] lg:max-w-[200px] xl:max-h-[180px] xl:max-w-[240px] 2xl:max-h-[210px] 2xl:max-w-[280px] ${sponsor.className || ""}`}
> />
<Image </NextLink>
src={sponsor.src}
alt={sponsor.alt}
width={sponsor.width}
height={sponsor.height}
className={`object-contain max-h-[80px] max-w-[120px] sm:max-h-[110px] sm:max-w-[150px] md:max-h-[130px] md:max-w-[180px] lg:max-h-[140px] lg:max-w-[200px] xl:max-h-[180px] xl:max-w-[240px] ${sponsor.className || ""}`}
/>
</NextLink>
))}
</div>
))} ))}
</div> </div>
</div> </div>
{/* Navigation dots */} <style jsx>{`
<div className="flex justify-center gap-3 py-4"> .ticker-track {
{slides.map((_, i) => ( animation: sponsors-ticker 36s linear infinite;
<button will-change: transform;
key={i} }
onClick={() => setCurrent(i)}
className={`w-3 h-3 rounded-full transition ${ @keyframes sponsors-ticker {
i === current from {
? "bg-[#00A3E0]" transform: translateX(-25%);
: "bg-[#1F5673] hover:bg-[#007CAB]/60" }
}`} to {
aria-label={`Slide ${i + 1}`} transform: translateX(0%);
/> }
))} }
</div> `}</style>
</div> </div>
); );
} }

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import Image from "next/image"; import Image from "next/image";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { vipnagorgialla } from "@/components/Vipnagorgialla"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
@@ -64,6 +64,7 @@ const slides: Slide[] = [
export default function TeaserCarousel() { export default function TeaserCarousel() {
const t = useTranslations("home.teaser"); const t = useTranslations("home.teaser");
const [current, setCurrent] = useState(0); const [current, setCurrent] = useState(0);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const next = useCallback( const next = useCallback(
() => setCurrent((c) => (c + 1) % slides.length), () => setCurrent((c) => (c + 1) % slides.length),
@@ -71,66 +72,91 @@ export default function TeaserCarousel() {
); );
const prev = () => setCurrent((c) => (c - 1 + slides.length) % slides.length); const prev = () => setCurrent((c) => (c - 1 + slides.length) % slides.length);
useEffect(() => { const restartAutoplay = useCallback(() => {
const id = setInterval(next, 5000); if (intervalRef.current) {
return () => clearInterval(id); clearInterval(intervalRef.current);
}
intervalRef.current = setInterval(next, 5000);
}, [next]); }, [next]);
useEffect(() => {
restartAutoplay();
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [restartAutoplay]);
const headingOnRight = Boolean(slides[current]?.flip);
return ( return (
<div className="border-b-3 border-[#1F5673]"> <div className="border-b-3 border-[#1F5673]">
{/* Sliding track */} {/* Slides (fade transition + hero lift effect) */}
<div className="relative h-[729px] overflow-hidden"> <div className="relative h-[729px] overflow-hidden">
<div {slides.map((slide, i) => {
className="flex h-full transition-transform duration-500 ease-in-out" const title = t(`${slide.key}.title`);
style={{ transform: `translateX(-${current * 100}%)` }} const description = t(`${slide.key}.description`);
> const isActive = i === current;
{slides.map((slide) => {
const title = t(`${slide.key}.title`);
const description = t(`${slide.key}.description`);
return (
<div key={slide.key} className="relative flex-none w-full h-full">
{/* Background image */}
<Image
src={slide.image}
alt={slide.imageAlt}
fill
className="object-cover object-center"
/>
{/* Overlay */}
<div
className={`absolute inset-0 ${slide.fullBrightness ? "" : "bg-gradient-to-r from-[#0E0F19]/90 via-[#0E0F19]/60 to-[#0E0F19]/20"}`}
/>
{/* Content */} return (
<div
key={slide.key}
className={`absolute inset-0 transition-opacity duration-700 ease-out ${
isActive ? "opacity-100" : "opacity-0 pointer-events-none"
}`}
>
{/* Background image */}
<Image
src={slide.image}
alt={slide.imageAlt}
fill
className="object-cover object-center"
/>
{/* Overlay */}
<div
className={`absolute inset-0 ${slide.fullBrightness ? "" : "bg-gradient-to-r from-[#0E0F19]/90 via-[#0E0F19]/60 to-[#0E0F19]/20"}`}
/>
{/* Content */}
<div
className={`relative grid grid-cols-1 md:grid-cols-2 h-full ${slide.flip ? "md:[direction:rtl]" : ""}`}
>
<div <div
className={`relative grid grid-cols-1 md:grid-cols-2 h-full ${slide.flip ? "md:[direction:rtl]" : ""}`} className={`flex flex-col justify-end px-8 py-8 md:px-12 md:py-10 ${slide.flip ? "md:[direction:ltr]" : ""}`}
>
{/* Title + description at bottom */}
<div
className={`flex flex-col gap-3 pb-16 ${slide.flip ? "md:items-end md:text-right md:ml-auto" : ""}`}
>
<Link href={slide.href}>
<h3
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2.5vw,5rem)] leading-none text-[#EEE5E5] hover:text-[#00A3E0] transition`}
>
{title}
</h3>
</Link>
<p
className={`text-[1.1rem] leading-relaxed md:text-[clamp(0.875rem,0.75rem+0.5vw,1.1rem)] text-[#EEE5E5] max-w-prose ${slide.flip ? "md:text-right" : ""}`}
>
{description}
</p>
</div>
</div>
{/* Hero image */}
<div
className={`hidden md:block relative overflow-hidden ${slide.flip ? "md:[direction:ltr]" : ""}`}
> >
<div <div
className={`flex flex-col justify-between px-8 py-8 md:px-12 md:py-10 ${slide.flip ? "md:[direction:ltr]" : ""}`} className="absolute inset-0 transition-transform duration-700 ease-out"
> style={{
{/* Heading at top */} transform: isActive
<h2 ? "translateY(0)"
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,1.5rem+4.5vw,4rem)] md:text-[64px] leading-none tracking-normal uppercase text-[#EEE5E5]`} : "translateY(110%)",
> }}
{highlightLAN(t("heading"))}
</h2>
{/* Title + description at bottom */}
<div className="flex flex-col gap-3 pb-16">
<Link href={slide.href}>
<h3
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2.5vw,5rem)] leading-none text-[#EEE5E5] hover:text-[#00A3E0] transition`}
>
{title}
</h3>
</Link>
<p className="text-[clamp(0.875rem,0.75rem+0.5vw,1.1rem)] text-[#EEE5E5] max-w-prose">
{description}
</p>
</div>
</div>
{/* Hero image */}
<div
className={`hidden md:block relative ${slide.flip ? "md:[direction:ltr]" : ""}`}
> >
<Image <Image
src={slide.hero} src={slide.hero}
@@ -141,20 +167,52 @@ export default function TeaserCarousel() {
</div> </div>
</div> </div>
</div> </div>
); </div>
})} );
})}
{/* Floating heading (mobile) */}
<div className="absolute top-5 inset-x-0 px-8 z-20 md:hidden pointer-events-none">
<h2
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+4.6vw,3rem)] leading-[0.95] tracking-normal uppercase text-[#EEE5E5] whitespace-normal break-words text-left max-w-[12ch]`}
>
{highlightLAN(t("heading"))}
</h2>
</div>
{/* Floating heading (desktop/tablet) */}
<div className="absolute top-8 inset-x-0 px-4 sm:px-6 md:px-12 z-20 hidden md:flex pointer-events-none">
<div
className="transition-[flex-grow] duration-700 ease-out"
style={{ flexGrow: headingOnRight ? 1 : 0 }}
/>
<h2
className={`${vipnagorgialla.className} font-bold italic text-[64px] leading-none tracking-normal uppercase text-[#EEE5E5] whitespace-normal [overflow-wrap:anywhere] text-center shrink`}
>
{highlightLAN(t("heading"))}
</h2>
<div
className="transition-[flex-grow] duration-700 ease-out"
style={{ flexGrow: headingOnRight ? 0 : 1 }}
/>
</div> </div>
{/* Arrow buttons */} {/* Arrow buttons */}
<button <button
onClick={prev} onClick={() => {
prev();
restartAutoplay();
}}
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition z-20" className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition z-20"
aria-label="Previous slide" aria-label="Previous slide"
> >
<span className="material-symbols-outlined">chevron_left</span> <span className="material-symbols-outlined">chevron_left</span>
</button> </button>
<button <button
onClick={next} onClick={() => {
next();
restartAutoplay();
}}
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition z-20" className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition z-20"
aria-label="Next slide" aria-label="Next slide"
> >
@@ -166,7 +224,10 @@ export default function TeaserCarousel() {
{slides.map((_, i) => ( {slides.map((_, i) => (
<button <button
key={i} key={i}
onClick={() => setCurrent(i)} onClick={() => {
setCurrent(i);
restartAutoplay();
}}
className={`w-3 h-3 rounded-full transition ${ className={`w-3 h-3 rounded-full transition ${
i === current i === current
? "bg-[#00A3E0]" ? "bg-[#00A3E0]"

View File

@@ -12,8 +12,7 @@ export default function middleware(request: NextRequest) {
if (location) { if (location) {
const url = new URL(location); const url = new URL(location);
// Keep localhost development redirects intact, but drop leaked :3000 // Keep localhost development redirects intact. If not then DO NOT!
// on public domains when upstream proxy forwards an internal port.
const isLocalhost = const isLocalhost =
url.hostname === "localhost" || url.hostname === "127.0.0.1"; url.hostname === "localhost" || url.hostname === "127.0.0.1";