Files
Artista-Filmcamp/src/components/booking/MobileBookingBar.tsx
T
Nick SzaunigandClaude Opus 5.5 7203cd1e6e 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]>
2026-10-01 23:53:18 +02:00

70 lines
2.6 KiB
TypeScript

"use client";
import { AnimatePresence, LazyMotion, domAnimation, m } from "motion/react";
import { useEffect, useState } from "react";
import { BookButton, useBooking } from "./BookingProvider";
/**
* Buchungsleiste fuer kleine Bildschirme. Erscheint erst, wenn der Hero
* verlassen ist, und verschwindet im Preisbereich und am Seitenende, damit
* sie dort keine Inhalte oder Bedienelemente verdeckt.
*/
export function MobileBookingBar({ priceLabel }: { priceLabel: string }) {
const { pricing } = useBooking();
const [heroVisible, setHeroVisible] = useState(true);
const [blocked, setBlocked] = useState(false);
useEffect(() => {
const hero = document.getElementById("top");
const blockers = ["preis", "site-footer"].map((id) => document.getElementById(id)).filter(Boolean) as HTMLElement[];
const visible = new Set<string>();
const heroObserver = new IntersectionObserver(([entry]) => setHeroVisible(entry?.isIntersecting ?? false));
const blockObserver = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) visible.add(entry.target.id);
else visible.delete(entry.target.id);
}
setBlocked(visible.size > 0);
});
if (hero) heroObserver.observe(hero);
blockers.forEach((b) => blockObserver.observe(b));
return () => {
heroObserver.disconnect();
blockObserver.disconnect();
};
}, []);
const show = !heroVisible && !blocked;
return (
<LazyMotion features={domAnimation}>
<AnimatePresence>
{show && (
<m.div
className="mobile-book-bar fixed inset-x-0 bottom-0 z-30 border-t border-line bg-plaster/95 backdrop-blur lg:hidden"
data-visible="true"
initial={{ y: "100%" }}
animate={{ y: 0 }}
exit={{ y: "100%" }}
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
>
<div className="container-edge flex items-center justify-between gap-4 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]">
<p className="text-sm leading-tight">
<span className="block font-semibold">Modelcamp 2027</span>
<span className="text-stone">{priceLabel}</span>
</p>
{pricing.checkoutOpen ? (
<BookButton className="btn btn-primary min-h-11 px-5 py-2 text-sm">Platz sichern</BookButton>
) : (
<a href="#preis" className="btn btn-quiet min-h-11 px-5 py-2 text-sm">
Zum Preis
</a>
)}
</div>
</m.div>
)}
</AnimatePresence>
</LazyMotion>
);
}