style: Hero, Leistungen und Abschnittsrhythmus ueberarbeitet

- Hero: Schlagzeile zweizeilig direkt unter dem Masthead, Buchung im
  ersten Bildschirm; Termin, Ort und Preis als Cover-Line-Leiste
- Leistungen: Inklusivleistungen in Bodoni ohne Linie unter jeder Zeile,
  Beispielablauf ruhiger, Hinweis unter statt neben der Ueberschrift
- Unterkunft ohne Split-Header, Ueber ARTISTA kompakter
- FAQ-Symbol gezeichnet statt Textzeichen, groesserer Abschnittsabstand
- Header auf Mobil ohne doppeltes "Modelcamp 2027"

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-28 17:46:30 +02:00
1 parent 00c7118c10
commit 2ef92db9ed
5 files changed
+80 -74

No files matched your search

+39 -28
View File
@@ -22,66 +22,77 @@ export function priceLine(pricing: PublicPricing): string {
/** /**
* Hero als Magazin-Cover: der Masthead "ARTISTA" ueberlappt das Leitbild, * Hero als Magazin-Cover: der Masthead "ARTISTA" ueberlappt das Leitbild,
* Termin, Ort und Preis stehen als Cover-Lines daneben. * Schlagzeile und Buchung stehen daneben und bleiben im ersten Bildschirm.
* Termin, Ort und Preis folgen als Cover-Lines in einer eigenen Leiste.
*/ */
export function Hero({ facts, pricing, showDrafts }: { facts: PublicCampFacts; pricing: PublicPricing; showDrafts: boolean }) { export function Hero({ facts, pricing, showDrafts }: { facts: PublicCampFacts; pricing: PublicPricing; showDrafts: boolean }) {
const place = facts.location ? `${facts.location}${facts.region ? `, ${facts.region}` : ""}` : "Italien"; const place = facts.location ? `${facts.location}${facts.region ? `, ${facts.region}` : ""}` : "Italien";
const coverLines = [
{ term: "Termin", value: dateLine(facts) },
{ term: "Ort", value: facts.location ?? "Italien, genauer Ort folgt" },
{ term: "Preis", value: priceLine(pricing) },
];
return ( return (
<section id="top" aria-labelledby="hero-title" className="relative pb-16 pt-4 md:pb-24"> <section id="top" aria-labelledby="hero-title" className="relative pb-20 pt-3 md:pb-28">
<div className="container-edge"> <div className="container-edge">
<p className="hero-fade flex items-baseline justify-between border-b border-ink pb-2 text-sm"> <p className="hero-fade flex items-baseline justify-between border-b border-ink pb-2 text-sm">
<span>Modelcamp 2027</span> <span>Modelcamp 2027</span>
<span>{place}</span> <span>{place}</span>
</p> </p>
<h1 id="hero-title" className="relative z-10 mt-3"> <h1 id="hero-title" className="relative z-10 mt-2 md:mt-3">
<span className="masthead hero-rise block" aria-hidden> <span className="masthead hero-rise block" aria-hidden>
ARTISTA ARTISTA
</span> </span>
<span className="sr-only">ARTISTA Modelcamp 2027 – {site.hero.headline}</span> <span className="sr-only">ARTISTA Modelcamp 2027 – {site.hero.headline}</span>
</h1> </h1>
<div className="mt-[-3.2vw] grid gap-8 md:grid-cols-12 md:gap-10"> <div className="mt-[-3.4vw] grid gap-9 md:grid-cols-12 md:gap-10">
<div className="hero-image md:col-span-7 lg:col-span-8"> <div className="hero-image md:col-span-7">
<MediaFrame <MediaFrame
media={site.hero.media} media={site.hero.media}
aspect="aspect-[4/5] md:aspect-[5/4] lg:aspect-[16/11]" aspect="aspect-[4/3] md:aspect-[4/5] lg:aspect-[7/6]"
sizes="(min-width: 1024px) 66vw, (min-width: 768px) 58vw, 100vw" sizes="(min-width: 768px) 58vw, 100vw"
showLabel={showDrafts} showLabel={showDrafts}
priority priority
/> />
</div> </div>
<div className="hero-fade flex flex-col justify-end md:col-span-5 lg:col-span-4"> <div className="hero-fade flex flex-col md:col-span-5 md:pt-[5.5vw] lg:col-start-8">
<p className="display text-[2.25rem] leading-[1.05] md:text-[2.75rem]">{site.hero.headline}</p> <p className="display text-[2.5rem] leading-[1.02] tracking-[-0.02em] md:text-[clamp(2.25rem,3.6vw,3.375rem)]">
<p className="mt-5 max-w-md text-lg text-ink/85">{site.hero.subline}</p> {/* Jeder Satz eine Zeile: bricht nie mitten im Gedanken um. */}
<dl className="mt-8 divide-y divide-line border-y border-line text-[0.9375rem]"> {site.hero.headline.split(/(?<=\.)\s+/).map((sentence) => (
<div className="flex justify-between gap-4 py-3"> <span key={sentence} className="block">
<dt className="text-stone">Termin</dt> {sentence}
<dd className="text-right">{dateLine(facts)}</dd> </span>
</div> ))}
<div className="flex justify-between gap-4 py-3"> </p>
<dt className="text-stone">Ort</dt> <p className="mt-6 max-w-[34ch] text-lg leading-relaxed text-ink/85">{site.hero.subline}</p>
<dd className="text-right">{facts.location ?? "Italien, genauer Ort folgt"}</dd> <div className="mt-9 flex flex-wrap items-center gap-x-7 gap-y-4">
</div>
<div className="flex justify-between gap-4 py-3">
<dt className="text-stone">Preis</dt>
<dd className="text-right">{priceLine(pricing)}</dd>
</div>
</dl>
<div className="mt-8 flex flex-wrap gap-3">
{pricing.checkoutOpen ? ( {pricing.checkoutOpen ? (
<BookButton className="btn btn-primary">Platz buchen</BookButton> <BookButton className="btn btn-primary px-7">Platz buchen</BookButton>
) : ( ) : (
<a href="#preis" className="btn btn-primary"> <a href="#preis" className="btn btn-primary px-7">
Preis und Buchungsstart Preis und Buchungsstart
</a> </a>
)} )}
<a href="#camp" className="btn btn-quiet"> <a href="#camp" className="link-quiet font-semibold">
Mehr über das Camp Mehr über das Camp
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<dl className="hero-fade mt-12 grid border-t border-ink sm:grid-cols-3 md:mt-16">
{coverLines.map((line, index) => (
<div
key={line.term}
className={`border-b border-line py-5 sm:border-b-0 sm:py-6 ${index > 0 ? "sm:border-l sm:pl-6 md:pl-8" : "sm:pr-6"}`}
>
<dt className="text-sm text-stone">{line.term}</dt>
<dd className="display mt-2 text-[1.5rem] leading-tight sm:text-[1.25rem] md:text-[1.375rem] lg:text-[1.75rem]">{line.value}</dd>
</div>
))}
</dl>
</div> </div>
</section> </section>
); );
+2 -2
View File
@@ -46,14 +46,14 @@ export function SiteHeader() {
return ( return (
<header <header
className={`sticky top-0 z-40 transition-colors duration-300 ${scrolled ? "bg-plaster/92 shadow-[0_1px_0_var(--color-line)] backdrop-blur" : "bg-transparent"}`} 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"}`}
> >
<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">
Zum Inhalt springen Zum Inhalt springen
</a> </a>
<div className="container-edge flex h-16 items-center justify-between gap-6"> <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"> <a href="#top" className="display text-xl tracking-tight" aria-label="ARTISTA Modelcamp 2027, zum Seitenanfang">
ARTISTA <span className="font-sans text-sm text-stone">Modelcamp 2027</span> ARTISTA <span className="hidden font-sans text-sm text-stone sm:inline">Modelcamp 2027</span>
</a> </a>
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<nav aria-label="Abschnitte" className="hidden lg:block"> <nav aria-label="Abschnitte" className="hidden lg:block">
+29 -35
View File
@@ -10,7 +10,7 @@ export function CampSection({ showDrafts }: { showDrafts: boolean }) {
const { camp } = site; const { camp } = site;
const showAudience = camp.audience.confirmed || showDrafts; const showAudience = camp.audience.confirmed || showDrafts;
return ( return (
<section id="camp" aria-labelledby="camp-title" className="py-20 md:py-32"> <section id="camp" aria-labelledby="camp-title" className="py-24 md:py-36">
<div className="container-edge grid gap-10 md:grid-cols-12"> <div className="container-edge grid gap-10 md:grid-cols-12">
<div className="md:col-span-6 lg:col-span-5"> <div className="md:col-span-6 lg:col-span-5">
<h2 id="camp-title" className="section-title">{camp.title}</h2> <h2 id="camp-title" className="section-title">{camp.title}</h2>
@@ -48,48 +48,44 @@ export function ServicesSection({ showDrafts }: { showDrafts: boolean }) {
const { services } = site; const { services } = site;
const included = services.included.filter((s) => s.confirmed || showDrafts); const included = services.included.filter((s) => s.confirmed || showDrafts);
return ( return (
<section id="leistungen" aria-labelledby="leistungen-title" className="on-dark bg-ink py-20 text-paper md:py-32"> <section id="leistungen" aria-labelledby="leistungen-title" className="on-dark bg-ink py-24 text-paper md:py-36">
<div className="container-edge"> <div className="container-edge">
<h2 id="leistungen-title" className="section-title max-w-3xl">{services.title}</h2> <div className="grid gap-14 md:grid-cols-12 md:gap-10">
<div className="mt-14 grid gap-12 md:grid-cols-2 md:gap-16"> <h2 id="leistungen-title" className="section-title md:col-span-5">{services.title}</h2>
<div> <div className="md:col-span-7 md:col-start-6 lg:col-span-6 lg:col-start-7">
<h3 className="border-b border-paper/30 pb-3 text-lg font-semibold">Im Preis enthalten</h3> <h3 className="text-[0.9375rem] font-semibold text-paper/75">Im Preis enthalten</h3>
<ul className="mt-2 divide-y divide-paper/15"> <ul className="mt-5 space-y-4">
{included.map((item) => ( {included.map((item) => (
<li key={item.text} className="py-4 text-lg"> <li key={item.text} className="display text-[1.625rem] leading-[1.2] md:text-[2rem]">
{item.text} {item.text}
{!item.confirmed && <DraftFlag show />} {!item.confirmed && <DraftFlag show />}
</li> </li>
))} ))}
</ul> </ul>
{!showDrafts && included.length < services.included.length && ( {!showDrafts && included.length < services.included.length && (
<p className="mt-4 text-paper/75">Weitere Leistungen veröffentlichen wir, sobald sie verbindlich feststehen.</p> <p className="mt-6 text-paper/75">Weitere Leistungen veröffentlichen wir, sobald sie verbindlich feststehen.</p>
)} )}
</div>
<div> <h3 className="mt-14 text-[0.9375rem] font-semibold text-paper/75">Nicht enthalten</h3>
<h3 className="border-b border-paper/30 pb-3 text-lg font-semibold">Nicht enthalten</h3> <p className="mt-3 max-w-xl text-lg text-paper/85">{services.excluded.join(", ")}.</p>
<ul className="mt-2 divide-y divide-paper/15">
{services.excluded.map((item) => (
<li key={item} className="py-4 text-lg text-paper/85">
{item}
</li>
))}
</ul>
</div> </div>
</div> </div>
<div className="mt-20 border-t border-paper/30 pt-10"> <div className="mt-24 md:mt-32">
<div className="flex flex-wrap items-baseline justify-between gap-4"> <h3 className="display max-w-2xl text-[2rem] leading-[1.1] md:text-[2.5rem]">So könnte ein Tag im Camp aussehen</h3>
<h3 className="display text-3xl">So könnte ein Tag im Camp aussehen</h3> <p className="mt-4 max-w-xl text-[0.9375rem] text-paper/75">{services.exampleSchedule.note}</p>
<p className="max-w-md text-[0.9375rem] text-paper/75">{services.exampleSchedule.note}</p> {/* Nummeriert, weil es eine Abfolge ist; ausdruecklich als Beispiel gekennzeichnet. */}
</div> <ol className="mt-12 grid border-t border-paper/30 md:grid-cols-3">
{/* Nummeriert, weil es eine Abfolge ist — ausdruecklich als Beispiel gekennzeichnet. */}
<ol className="mt-10 grid gap-8 md:grid-cols-3">
{services.exampleSchedule.steps.map((step, index) => ( {services.exampleSchedule.steps.map((step, index) => (
<li key={step.title} className="border-t border-paper/30 pt-5"> <li
<span className="display text-5xl text-paper/40">{index + 1}</span> key={step.title}
<p className="mt-3 text-lg font-semibold">{step.title}</p> className={`border-b border-paper/15 py-7 md:border-b-0 md:py-9 ${index > 0 ? "md:border-l md:border-paper/15 md:pl-8" : "md:pr-8"}`}
<p className="mt-2 text-paper/80">{step.text}</p> >
<p className="flex items-baseline gap-4">
<span className="text-sm tabular-nums text-paper/60">{index + 1}</span>
<span className="display text-[1.75rem] italic">{step.title}</span>
</p>
<p className="mt-3 max-w-xs text-paper/80">{step.text}</p>
</li> </li>
))} ))}
</ol> </ol>
@@ -102,12 +98,10 @@ export function ServicesSection({ showDrafts }: { showDrafts: boolean }) {
export function AccommodationSection({ facts, showDrafts }: { facts: PublicCampFacts; showDrafts: boolean }) { export function AccommodationSection({ facts, showDrafts }: { facts: PublicCampFacts; showDrafts: boolean }) {
const { accommodation } = site; const { accommodation } = site;
return ( return (
<section id="unterkunft" aria-labelledby="unterkunft-title" className="py-20 md:py-32"> <section id="unterkunft" aria-labelledby="unterkunft-title" className="py-24 md:py-36">
<div className="container-edge"> <div className="container-edge">
<div className="grid gap-8 md:grid-cols-12"> <h2 id="unterkunft-title" className="section-title">{accommodation.title}</h2>
<h2 id="unterkunft-title" className="section-title md:col-span-6">{accommodation.title}</h2> <p className="display mt-6 max-w-2xl text-[1.5rem] leading-[1.25] text-ink/85 md:text-[1.875rem]">{accommodation.lead}</p>
<p className="text-lg text-ink/85 md:col-span-5 md:col-start-8 md:self-end">{accommodation.lead}</p>
</div>
<Gallery images={accommodation.images} showLabels={showDrafts} /> <Gallery images={accommodation.images} showLabels={showDrafts} />
<div className="mt-12 grid gap-8 md:grid-cols-12"> <div className="mt-12 grid gap-8 md:grid-cols-12">
<div className="md:col-span-7"> <div className="md:col-span-7">
+5 -4
View File
@@ -62,16 +62,17 @@ function faqs(facts: PublicCampFacts, pricing: PublicPricing): Faq[] {
export function FaqSection({ facts, pricing }: { facts: PublicCampFacts; pricing: PublicPricing }) { export function FaqSection({ facts, pricing }: { facts: PublicCampFacts; pricing: PublicPricing }) {
return ( return (
<section id="fragen" aria-labelledby="fragen-title" className="py-20 md:py-32"> <section id="fragen" aria-labelledby="fragen-title" className="py-24 md:py-36">
<div className="container-edge grid gap-10 md:grid-cols-12"> <div className="container-edge grid gap-10 md:grid-cols-12">
<h2 id="fragen-title" className="section-title md:col-span-4">{site.faq.title}</h2> <h2 id="fragen-title" className="section-title md:col-span-4">{site.faq.title}</h2>
<div className="md:col-span-8"> <div className="md:col-span-8">
{faqs(facts, pricing).map((item) => ( {faqs(facts, pricing).map((item) => (
<details key={item.q} className="group border-b border-line py-1 first:border-t"> <details key={item.q} className="group border-b border-line py-1 first:border-t">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 py-4 text-lg font-semibold [&::-webkit-details-marker]:hidden"> <summary className="flex cursor-pointer list-none items-center justify-between gap-6 py-5 text-lg font-semibold transition-colors hover:text-rosso [&::-webkit-details-marker]:hidden">
{item.q} {item.q}
<span aria-hidden className="text-2xl font-normal text-rosso transition-transform duration-200 group-open:rotate-45"> <span aria-hidden className="relative h-4 w-4 shrink-0 text-rosso transition-transform duration-300 ease-[var(--ease-editorial)] group-open:rotate-45">
+ <span className="absolute inset-x-0 top-1/2 h-[1.5px] -translate-y-1/2 bg-current" />
<span className="absolute inset-y-0 left-1/2 w-[1.5px] -translate-x-1/2 bg-current" />
</span> </span>
</summary> </summary>
<div className="max-w-2xl pb-5 text-ink/85">{item.a}</div> <div className="max-w-2xl pb-5 text-ink/85">{item.a}</div>
+5 -5
View File
@@ -6,7 +6,7 @@ import { VideoSlot } from "../VideoSlot";
export function TeachersSection({ showDrafts }: { showDrafts: boolean }) { export function TeachersSection({ showDrafts }: { showDrafts: boolean }) {
const { teachers } = site; const { teachers } = site;
return ( return (
<section id="dozentinnen" aria-labelledby="dozentinnen-title" className="bg-paper py-20 md:py-32"> <section id="dozentinnen" aria-labelledby="dozentinnen-title" className="bg-paper py-24 md:py-36">
<div className="container-edge"> <div className="container-edge">
<h2 id="dozentinnen-title" className="section-title">{teachers.title}</h2> <h2 id="dozentinnen-title" className="section-title">{teachers.title}</h2>
<div className="mt-14 grid gap-16 md:grid-cols-12 md:gap-10"> <div className="mt-14 grid gap-16 md:grid-cols-12 md:gap-10">
@@ -39,10 +39,10 @@ export function TeachersSection({ showDrafts }: { showDrafts: boolean }) {
export function AboutSection({ showDrafts }: { showDrafts: boolean }) { export function AboutSection({ showDrafts }: { showDrafts: boolean }) {
const { about } = site; const { about } = site;
return ( return (
<section id="artista" aria-labelledby="artista-title" className="py-20 md:py-32"> <section id="artista" aria-labelledby="artista-title" className="py-20 md:py-28">
<div className="container-edge grid gap-8 md:grid-cols-12"> <div className="container-edge grid gap-6 border-t border-ink pt-10 md:grid-cols-12 md:gap-10 md:pt-14">
<h2 id="artista-title" className="section-title md:col-span-5">{about.title}</h2> <h2 id="artista-title" className="display text-[2.25rem] leading-none md:col-span-5 md:text-[3rem]">{about.title}</h2>
<div className="prose-body md:col-span-6 md:col-start-7"> <div className="prose-body text-lg md:col-span-6 md:col-start-7 md:pt-2">
{about.paragraphs.length > 0 ? ( {about.paragraphs.length > 0 ? (
about.paragraphs.map((p) => <p key={p}>{p}</p>) about.paragraphs.map((p) => <p key={p}>{p}</p>)
) : ( ) : (