Files
Artista-Filmcamp/src/components/Hero.tsx
T
Nick SzaunigandClaude Opus 5.5 1ca09cbaef feat: Logo deutlich größer und auf jedem Untergrund sichtbar
Das hochgeladene logo.png ist die helle Fassung und war auf der hellen Kopfzeile und im Fuß bis auf das goldene A unsichtbar. Die Seite misst jetzt die Helligkeit (sharp) und ordnet die Datei selbst zu; fehlt eine passende Fassung, wird per CSS abgedunkelt bzw. aufgehellt. Über dem Foto bis 176 px hoch mit weichem Schatten, nach dem Scrollen 80 px. Raster-Logos laufen über die Bildoptimierung (Original 5378 px, 313 KB).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-06 07:15:09 +02:00

98 lines
4.7 KiB
TypeScript

import Image from "next/image";
import { site, type Media } from "@/content/site";
import type { PublicCampFacts } from "@/lib/camp";
import { formatEuroShort } from "@/lib/money";
import type { PublicPricing } from "@/lib/public-page";
import { formatIsoDateLong } from "@/lib/time";
import { MediaFrame } from "./MediaFrame";
import { BookButton } from "./booking/BookingProvider";
export function dateLine(facts: PublicCampFacts): string | null {
if (!facts.startDate) return null;
if (!facts.endDate) return `ab ${formatIsoDateLong(facts.startDate)}`;
// Gleicher Monat: "8. bis 15. Mai 2027" statt zweimal Monat und Jahr.
if (facts.startDate.slice(0, 7) === facts.endDate.slice(0, 7)) {
return `${Number(facts.startDate.slice(8, 10))}. bis ${formatIsoDateLong(facts.endDate)}`;
}
return `${formatIsoDateLong(facts.startDate)} bis ${formatIsoDateLong(facts.endDate)}`;
}
/** Kurzform fuer die mobile Buchungsleiste. Ohne Startdatum des Verkaufs. */
export function priceLine(pricing: PublicPricing): string {
if (pricing.phase === "REGULAR" && pricing.currentCents !== null) return `${formatEuroShort(pricing.currentCents)} pro Person`;
return `Early Bird ${formatEuroShort(pricing.earlyBirdCents)} statt ${formatEuroShort(pricing.regularCents)}`;
}
function Backdrop({ media, video }: { media: Media; video: string | null }) {
if (media.kind !== "image") {
return <MediaFrame media={media} aspect="h-full w-full" sizes="100vw" showLabel={false} />;
}
return (
<>
<Image src={media.src} alt={media.alt} fill priority sizes="100vw" className="hero-zoom object-cover" />
{video && (
// Stumm und ohne Ton-Autoplay; bei reduzierter Bewegung blendet
// globals.css das Video aus und das Standbild bleibt.
<video src={video} poster={media.src} autoPlay muted loop playsInline preload="metadata" aria-hidden className="hero-video absolute inset-0 h-full w-full object-cover" />
)}
</>
);
}
/**
* Headerbild ueber die volle Breite und Hoehe, Titel und Buchung mittig,
* wie bei der Filmcamp-Referenz. Bewusst ohne Preis: der steht im
* Preisabschnitt.
*/
export function Hero({ pricing, media, video }: { pricing: PublicPricing; media: Media; video: string | null }) {
const { hero } = site;
return (
<section id="top" aria-labelledby="hero-title" className="on-dark relative isolate flex min-h-[100svh] items-center overflow-hidden bg-ink text-paper">
<div className="absolute inset-0 -z-10">
<Backdrop media={media} video={video} />
<div aria-hidden className="absolute inset-0 bg-[linear-gradient(180deg,rgb(35_33_31/0.6)_0%,rgb(35_33_31/0.45)_45%,rgb(35_33_31/0.75)_100%)]" />
</div>
<div className="container-edge flex flex-col items-center pb-20 pt-44 text-center md:pt-60">
<h1 id="hero-title" className="hero-fade max-w-4xl text-[clamp(2.75rem,8vw,6.25rem)] font-extrabold leading-[0.98] tracking-[-0.03em]">
<span className="sr-only">ARTISTA </span>
{hero.title}
</h1>
<p className="hero-fade hero-fade-late mt-7 max-w-2xl text-lg text-paper/90 md:text-xl">{hero.subline}</p>
<div className="hero-fade hero-fade-later mt-10 flex flex-wrap justify-center gap-3">
{pricing.checkoutOpen ? (
<BookButton className="btn btn-primary px-8 text-lg">{hero.cta}</BookButton>
) : (
<a href="#preis" className="btn btn-primary px-8 text-lg">
{hero.cta}
</a>
)}
<a href="#erwartet" className="btn btn-quiet px-8 text-lg">
Was dich erwartet
</a>
</div>
</div>
<a href="#termin" aria-label="Weiter zum Termin" className="hero-fade hero-fade-later absolute bottom-6 left-1/2 -translate-x-1/2 p-2 text-paper/80 hover:text-paper">
<svg viewBox="0 0 24 24" className="hero-bounce h-7 w-7" aria-hidden>
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</section>
);
}
/** Dunkle Leiste unter dem Header mit dem Termin, wie bei der Referenz. */
export function DateBar({ facts }: { facts: PublicCampFacts }) {
const dates = dateLine(facts);
if (!dates) return null;
return (
<section id="termin" aria-label="Termin" className="on-dark bg-ink py-10 text-center text-paper md:py-14">
<p className="container-edge text-xl font-semibold md:text-2xl">
Verbringe eine unvergessliche Woche mit uns vom <span className="text-sand">{dates}</span>
{facts.location && <span className="mt-2 block text-base font-normal text-paper/75 md:text-lg">{facts.location}{facts.region ? `, ${facts.region}` : ""}</span>}
</p>
</section>
);
}