feat: Onepage nach Filmcamp-Gliederung umgebaut

- Figtree als einzige Schrift, Logo mittig in fester Kopfzeile
- Headerbild ueber volle Breite und Hoehe, ohne Preis, mit Terminleiste
- "Was dich erwartet" stellt die Villa mit animierter Bilderfolge vor,
  darunter Tagesablauf als Karussell (Programm folgt als PDF)
- Preis: regulaerer Preis durchgestrichen, kein Verkaufsstartdatum,
  Ueberschrift "Jetzt Platz sichern mit Early Bird"
- Team als "Mit dabei", danach drei Reels im Hochformat
- Logo, Headerbild, Portraets und Reels ueber feste Dateinamen im
  Medienverzeichnis (docs/MEDIEN.md), ohne Neustart austauschbar
- Widerrufsbelehrung im Admin pflegbar, Seite /widerruf, Live-Verkauf
  ohne Freigabe gesperrt
- Anreise: Abholung am Bahnhof Firenze Santa Maria Novella
- Booking-Link entfernt, Gedankenstriche aus Kundentexten entfernt

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-01 23:53:18 +02:00
1 parent 3cba3e2341
commit 7203cd1e6e
37 files changed
+962 -517

No files matched your search

+64 -42
View File
@@ -3,8 +3,19 @@
import { LazyMotion, domAnimation, m } from "motion/react";
import { useEffect, useRef, useState } from "react";
import { NAV_ITEMS } from "@/content/site";
import { Logo } from "./Logo";
export function SiteHeader() {
const LEFT = NAV_ITEMS.slice(0, 3);
const RIGHT = NAV_ITEMS.slice(3);
type NavItem = (typeof NAV_ITEMS)[number];
/**
* Kopfzeile wie bei der Filmcamp-Referenz: Logo mittig, Abschnitte links und
* rechts. Ueber dem Headerbild transparent mit heller Schrift, nach dem
* Scrollen auf hellem Grund.
*/
export function SiteHeader({ logoLight, logoDark }: { logoLight: string | null; logoDark: string | null }) {
const [active, setActive] = useState<string | null>(null);
const [scrolled, setScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
@@ -44,55 +55,66 @@ export function SiteHeader() {
return () => document.removeEventListener("keydown", onKey);
}, [menuOpen]);
const solid = scrolled || menuOpen;
const navList = (items: readonly NavItem[], align: string) => (
<ul className={`flex items-center gap-7 text-[0.9375rem] font-medium ${align}`}>
{items.map((item) => (
<li key={item.id} className="relative">
<a href={`#${item.id}`} aria-current={active === item.id ? "location" : undefined} className="block py-2 opacity-85 transition-opacity hover:opacity-100 aria-[current]:opacity-100">
{item.label}
</a>
{active === item.id && (
<m.span layoutId="nav-indicator" className="absolute inset-x-0 -bottom-0.5 h-0.5 rounded-full bg-current" transition={{ type: "spring", stiffness: 380, damping: 34 }} />
)}
</li>
))}
</ul>
);
return (
<header
className={`sticky top-0 z-40 transition-colors duration-300 ${scrolled ? "bg-plaster/95 shadow-[0_1px_0_var(--color-line)] backdrop-blur" : "bg-transparent"}`}
className={`fixed inset-x-0 top-0 z-40 transition-[background-color,color,box-shadow] duration-300 ${
solid ? "bg-plaster/95 text-ink shadow-[0_1px_0_var(--color-line)] backdrop-blur" : "on-dark bg-transparent text-paper"
}`}
>
<a href="#inhalt" className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-3 focus:z-50 focus:bg-paper focus:px-3 focus:py-2">
<a href="#inhalt" className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-3 focus:z-50 focus:bg-paper focus:px-3 focus:py-2 focus:text-ink">
Zum Inhalt springen
</a>
<div className="container-edge flex h-16 items-center justify-between gap-6">
<a href="#top" className="display text-xl tracking-tight" aria-label="ARTISTA Modelcamp 2027, zum Seitenanfang">
ARTISTA <span className="hidden font-sans text-sm text-stone sm:inline">Modelcamp 2027</span>
</a>
<LazyMotion features={domAnimation}>
<nav aria-label="Abschnitte" className="hidden lg:block">
<ul className="flex items-center gap-6 text-[0.9375rem]">
{NAV_ITEMS.map((item) => (
<li key={item.id} className="relative">
<a
href={`#${item.id}`}
aria-current={active === item.id ? "location" : undefined}
className="block py-2 text-ink/80 hover:text-ink aria-[current]:text-ink"
>
{item.label}
</a>
{active === item.id && (
<m.span layoutId="nav-indicator" className="absolute inset-x-0 -bottom-0.5 h-px bg-rosso" transition={{ type: "spring", stiffness: 380, damping: 34 }} />
)}
</li>
))}
</ul>
</nav>
</LazyMotion>
<div className="flex items-center gap-3">
<a href="#preis" className="btn btn-primary hidden min-h-10 px-5 py-2 text-sm sm:inline-flex">
Platz buchen
<LazyMotion features={domAnimation}>
<div className="container-edge grid h-[4.5rem] grid-cols-[1fr_auto_1fr] items-center gap-4">
<div className="flex items-center">
<button
ref={menuButton}
type="button"
className="inline-flex h-11 items-center rounded-full border border-current/40 px-4 text-sm font-semibold xl:hidden"
aria-expanded={menuOpen}
aria-controls="mobile-menu"
onClick={() => setMenuOpen((v) => !v)}
>
{menuOpen ? "Schließen" : "Menü"}
</button>
<nav aria-label="Abschnitte" className="hidden xl:block">
{navList(LEFT, "justify-start")}
</nav>
</div>
<a href="#top" aria-label="ARTISTA Modelcamp 2027, zum Seitenanfang" className="flex h-8 items-center text-xl md:h-10 md:text-2xl">
<Logo src={solid ? logoDark : logoLight} />
</a>
<button
ref={menuButton}
type="button"
className="inline-flex h-11 items-center rounded-full border border-ink/30 px-4 text-sm font-semibold lg:hidden"
aria-expanded={menuOpen}
aria-controls="mobile-menu"
onClick={() => setMenuOpen((v) => !v)}
>
{menuOpen ? "Schließen" : "Menü"}
</button>
<div className="flex items-center justify-end gap-7">
<nav aria-label="Weitere Abschnitte" className="hidden xl:block">
{navList(RIGHT, "justify-end")}
</nav>
<a href="#preis" className="btn btn-primary hidden min-h-10 px-5 py-2 text-sm sm:inline-flex">
Platz sichern
</a>
</div>
</div>
</div>
</LazyMotion>
{menuOpen && (
<nav id="mobile-menu" aria-label="Abschnitte" className="border-t border-line bg-plaster lg:hidden">
<nav id="mobile-menu" aria-label="Abschnitte" className="border-t border-line bg-plaster text-ink xl:hidden">
<ul className="container-edge py-4">
{NAV_ITEMS.map((item, index) => (
<li key={item.id}>