315 lines
9.5 KiB
TypeScript
315 lines
9.5 KiB
TypeScript
"use client"
|
|
|
|
import dynamic from 'next/dynamic';
|
|
|
|
|
|
|
|
import { jgs7 } from '@/fonts';
|
|
import { Translations } from "@/i18n/translations";
|
|
import { cn } from "@/lib/utils";
|
|
import { ReactElement, useEffect, useRef } from "react";
|
|
import { MainpageNav } from './nav';
|
|
import { NewsletterPopup } from './newsletter-form';
|
|
import { useTheme } from "./providers";
|
|
import { Skeleton } from './ui/skeleton';
|
|
|
|
function Heading({ children }: { children: ReactElement | string }) {
|
|
return <h2 className="text-5xl font-bold tracking-tighter max-w-3xl mx-auto">{children}</h2>
|
|
}
|
|
|
|
function Subheading({ children }: { children: ReactElement | string }) {
|
|
return <h3 className="text-3xl font-bold tracking-tighter max-w-3xl mx-auto">{children}</h3>
|
|
}
|
|
|
|
function TinyHeading({ children }: { children: ReactElement | string }) {
|
|
return <h4 className="text-lg font-bold tracking-tighter max-w-3xl mx-auto mb-2">{children}</h4>
|
|
}
|
|
|
|
function TinyTextWrapper({ children }: { children: ReactElement }) {
|
|
return <div className="text-sm text-muted-foreground max-w-3xl mx-auto whitespace-pre-line">
|
|
{children}
|
|
</div>
|
|
}
|
|
|
|
|
|
function TextWrapper({ children }: { children: ReactElement }) {
|
|
return <div className="text-lg text-muted-foreground max-w-3xl mx-auto whitespace-pre-line">
|
|
{children}
|
|
</div>
|
|
}
|
|
|
|
function NewSection({
|
|
id,
|
|
children,
|
|
after
|
|
}: {
|
|
id: string
|
|
children: ReactElement;
|
|
after?: ReactElement;
|
|
}) {
|
|
return (<section id={id} className="bg-background">
|
|
<div className="container mx-auto px-4 gap-6 flex flex-col">
|
|
{children}
|
|
{after}
|
|
</div>
|
|
</section>)
|
|
}
|
|
|
|
function Section({
|
|
id,
|
|
title,
|
|
paragraphs,
|
|
after
|
|
}: {
|
|
id: string
|
|
title: string;
|
|
paragraphs: ReactElement;
|
|
after?: ReactElement;
|
|
}) {
|
|
return (<section id={id} className="bg-background">
|
|
<div className="container mx-auto px-4">
|
|
<h2 className="text-4xl font-bold tracking-tighter max-w-3xl mx-auto mb-2">{title}</h2>
|
|
<div className="text-lg text-muted-foreground max-w-3xl mx-auto whitespace-pre-line">
|
|
{paragraphs}
|
|
</div>
|
|
{after}
|
|
</div>
|
|
</section>)
|
|
}
|
|
|
|
function getSource({
|
|
src,
|
|
type
|
|
}: {
|
|
src: string;
|
|
type: 'mp4' | 'webm' | 'ogv'
|
|
}) {
|
|
const sourceType = `video/${type}`
|
|
return [
|
|
<source
|
|
key={`mobile-${type}`}
|
|
media="(max-width: 640px)"
|
|
src={src.replace('.mp4', `_mobile.${type}`)}
|
|
type={sourceType}
|
|
/>,
|
|
|
|
<source
|
|
key={`tablet-${type}`}
|
|
media="(max-width: 1024px)"
|
|
src={src.replace('.mp4', `_tablet.${type}`)}
|
|
type={sourceType}
|
|
/>,
|
|
|
|
<source
|
|
key={`hd-${type}`}
|
|
media="(max-width: 1920px)"
|
|
src={src.replace('.mp4', `_hd.${type}`)}
|
|
type={sourceType}
|
|
/>,
|
|
|
|
<source
|
|
key={`twok-${type}`}
|
|
media="(max-width: 2560px)"
|
|
src={src.replace('.mp4', `_twok.${type}`)}
|
|
type={sourceType}
|
|
/>,
|
|
|
|
<source
|
|
key={`uhd-${type}`}
|
|
media="(max-width: 3840px)"
|
|
src={src.replace('.mp4', `_uhd.${type}`)}
|
|
type={sourceType}
|
|
/>,
|
|
|
|
<source
|
|
key={`full-${type}`}
|
|
media="(min-width: 3841px)"
|
|
src={src.replace('.mp4', `_full.${type}`)}
|
|
type={sourceType}
|
|
/>,
|
|
];
|
|
}
|
|
|
|
function Video({ sourceBase, hidden }: {
|
|
sourceBase: string;
|
|
hidden: boolean;
|
|
}) {
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!videoRef.current || hidden) return;
|
|
const handleScroll = () => {
|
|
if (!videoRef.current || hidden) return;
|
|
|
|
videoRef.current.play();
|
|
const scrolled = window.scrollY;
|
|
videoRef.current.style.willChange = "transform";
|
|
videoRef.current.style.transform = `translateY(${scrolled * 0.5}px)`;
|
|
videoRef.current.style.willChange = "unset";
|
|
};
|
|
|
|
const throttledHandleScroll = () => {
|
|
requestAnimationFrame(handleScroll);
|
|
};
|
|
|
|
window.addEventListener("scroll", throttledHandleScroll);
|
|
handleScroll();
|
|
return () => window.removeEventListener("scroll", throttledHandleScroll);
|
|
}, [hidden]);
|
|
|
|
const sources = [...getSource({ src: sourceBase, type: 'mp4' }), ...getSource({ src: sourceBase, type: 'ogv' }), ...getSource({ src: sourceBase, type: 'webm' })]
|
|
|
|
return (
|
|
<video
|
|
ref={videoRef}
|
|
preload="auto"
|
|
autoPlay
|
|
muted
|
|
loop
|
|
playsInline
|
|
webkit-playsinline="true"
|
|
x5-playsinline="true"
|
|
className={cn("w-full h-full object-contain parallax-video", {
|
|
hidden,
|
|
})}
|
|
>
|
|
{sources.map(x => x)}
|
|
</video>
|
|
);
|
|
}
|
|
|
|
|
|
const LazyLeafletMap = dynamic(() => import('./event-map.lazy'), {
|
|
ssr: false,
|
|
loading: () => <Skeleton className="mt-8 h-[368px] w-full" />
|
|
})
|
|
|
|
export default function LandingPage(
|
|
{ t }: { t: Translations }
|
|
) {
|
|
|
|
const { theme } = useTheme()
|
|
return (
|
|
<div>
|
|
<MainpageNav t={t} />
|
|
<main className="flex flex-col min-h-screen grid-gap-10 gap-10 pb-12">
|
|
|
|
<section id="hero" className="h-screen relative overflow-hidden dark:bg-black light:bg-white ">
|
|
<div className="absolute inset-0 opacity-80">
|
|
<Video sourceBase="/videos/ceboola_gradient.mp4" hidden={theme === 'light'} />
|
|
<Video sourceBase="/videos/ceboola_gradient-white.mp4" hidden={theme === "dark"} />
|
|
</div>
|
|
<div className="relative z-10 container mx-auto px-4 h-full flex items-center justify-center">
|
|
<div className={`text-center ${jgs7.className}`}>
|
|
<h1 className="text-5xl sm:text-6xl md:text-8xl font-bold tracking-tighter light:text-background">{t.hero.title}</h1>
|
|
<p className="mt-2 text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl-text:7xl 2xl:text-8xl text-primary">{t.hero.subtitle}</p>
|
|
<p className="mt-2 text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl-text:7xl 2xl:text-8xl text-primary ">{t.details.when.date}</p>
|
|
<div className='flex flex-col space-y-4 max-w-20 items-center justify-center m-auto'>
|
|
<NewsletterPopup t={t} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<NewSection id="about">
|
|
<>
|
|
<div>
|
|
<Heading>{t.about.title}</Heading>
|
|
</div>
|
|
<section>
|
|
<TextWrapper><p>{t.about.description}</p></TextWrapper>
|
|
</section>
|
|
<section>
|
|
<Subheading>{t.details.when.title}</Subheading>
|
|
<TextWrapper><p className={`text-primary text-3xl ${jgs7.className}`}>{t.details.when.date}</p></TextWrapper>
|
|
<TextWrapper><p>{t.details.when.extra}</p></TextWrapper>
|
|
</section>
|
|
<section>
|
|
<Subheading>{t.details.where.title}</Subheading>
|
|
<TextWrapper><p>{t.details.where.location}</p></TextWrapper>
|
|
<LazyLeafletMap t={t} />
|
|
</section>
|
|
</>
|
|
</NewSection>
|
|
|
|
<NewSection id="tickets">
|
|
<>
|
|
<div>
|
|
<Heading>{t.tickets.title}</Heading>
|
|
</div>
|
|
<section>
|
|
<TextWrapper><p>{t.tickets.status}</p></TextWrapper>
|
|
</section>
|
|
</>
|
|
</NewSection>
|
|
|
|
|
|
<NewSection id="cfp">
|
|
<>
|
|
<div>
|
|
<Heading>{t.cfp.title}</Heading>
|
|
</div>
|
|
<section>
|
|
<TextWrapper><p>{t.cfp.status}</p></TextWrapper>
|
|
</section>
|
|
</>
|
|
</NewSection>
|
|
|
|
<NewSection id="faq">
|
|
<>
|
|
<div>
|
|
<Heading>{t.details.title}</Heading>
|
|
</div>
|
|
<section>
|
|
<Subheading>{t.faq.accommodation.title}</Subheading>
|
|
<TextWrapper><p>{t.faq.accommodation.description}</p></TextWrapper>
|
|
</section>
|
|
<section>
|
|
<Subheading>{t.faq.accesibility.title}</Subheading>
|
|
<TextWrapper><p>{t.faq.accesibility.description} <a href="mailto:orga@cebula.camp">{t.contact.email}</a></p></TextWrapper>
|
|
</section>
|
|
<section>
|
|
<Subheading>{t.faq.food.title}</Subheading>
|
|
<TextWrapper><p>{t.faq.food.description}</p></TextWrapper>
|
|
</section>
|
|
<section>
|
|
<Subheading>{t.faq.transport.title}</Subheading>
|
|
<TextWrapper><p>{t.faq.transport.description}</p></TextWrapper>
|
|
</section>
|
|
<section>
|
|
<Subheading>{t.faq.documents.title}</Subheading>
|
|
<TextWrapper><p>
|
|
<a className='hover:text-primary' href={`/pages/rules`}>📜 {t.faq.documents.rules}</a></p></TextWrapper>
|
|
<TextWrapper><p>
|
|
<a className='hover:text-primary' href={`/pages/privacy`}>📜 {t.faq.documents.privacyPolicy}</a></p></TextWrapper>
|
|
</section>
|
|
</>
|
|
</NewSection>
|
|
|
|
<NewSection id="contact">
|
|
<>
|
|
<div>
|
|
<Heading>{t.contact.title}</Heading>
|
|
</div>
|
|
<section>
|
|
<TextWrapper><p><a href={`mailto:${t.contact.email}`}>{t.contact.email}</a></p></TextWrapper>
|
|
</section>
|
|
</>
|
|
</NewSection>
|
|
|
|
<NewSection id="credits">
|
|
<>
|
|
<section className='text-sm'>
|
|
<TinyHeading>{t.credits.title}</TinyHeading>
|
|
<TinyTextWrapper><p>{t.credits.usedFonts}</p></TinyTextWrapper>
|
|
<TinyTextWrapper><p><a className="hover:underline" href="https://velvetyne.fr/fonts/jgs-font/">{t.credits.jgs7}</a></p></TinyTextWrapper>
|
|
<TinyTextWrapper><p><a className="hover:underline" href="https://fonts.google.com/specimen/Oxanium">{t.credits.oxanium}</a></p></TinyTextWrapper>
|
|
</section>
|
|
</>
|
|
</NewSection>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|
|
|