Die Anfrageseite lief unter der Kopfleiste durch

Die Kopfleiste des Layouts ist fixed und schwebt: 56 px hoch, 16 px vom Rand,
also rund 72 px hoch. Die Seite begann mit pt-14 — 56 px — und schob ihre
Ueberschrift damit zwangslaeufig darunter. Sie haelt jetzt denselben Abstand
wie die Startseite, und dieser Abstand ist der Grund fuer die Zahl, nicht
Geschmack.

Dabei zwei weitere Dinge behoben, die beim Nachsehen auffielen: die Seite
brachte ein zweites <main> mit, obwohl das Layout bereits eines stellt (pro
Dokument darf es nur eines geben), und ein `sm:col-span-2` am x-ui.input haette
nichts getan — das Bauteil reicht mitgegebene Klassen an das INPUT weiter, nicht
an seinen Rahmen, und der Rahmen ist das Rasterkind.

Und die Seite steht jetzt in der Formensprache der Startseite statt daneben:
derselbe warme Schimmer hinter der Ueberschrift, Haarlinien-Karten,
lbl-Etiketten, gestaffelte Einblendungen, die echten Bauteile fuer Feld und
Knopf statt handgeschriebener Klassen.

Neu daneben: was nach dem Absenden passiert. Das ist die Frage, die jeder hat,
der ein Formular vor sich sieht, und die ein Formular allein nicht beantwortet
— bei Enterprise steht dort, dass Speicher, Nutzerzahl und Sicherung die
Maschine ergeben, sonst die Antwort am selben Werktag. Versprochen wird nichts,
was nicht schon vorher dastand.

Am Bildschirm nachgesehen, nicht nur im Test: Ueberschrift frei unter der
Kopfleiste, zwei Spalten ab lg, Formular und Spalte in einer Flucht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
nexxo 2026-08-02 00:04:03 +02:00
parent 413826f3d7
commit c062776a5b
1 changed files with 159 additions and 93 deletions

View File

@ -5,110 +5,176 @@
schon entschieden zu fragen also steht hier ein Formular und keine zweite
Verkaufsseite. Vorher zeigte der Knopf auf `mailto:`, was auf den meisten
Geräten sichtbar gar nichts tut. Siehe App\Http\Controllers\ContactController.
Zum Aufbau, nach dem Umbau vom 1. August 2026:
Die Kopfleiste des Layouts ist `fixed` und schwebt (56 px hoch, 16 px vom
Rand). Diese Seite begann mit `pt-14` 56 px und lief damit zwangsläufig
darunter. Der Einstieg hält jetzt denselben Abstand wie die Startseite
(`pt-32`, ab `lg` mehr), und dieser Abstand ist der Grund für die Zahl, nicht
Geschmack.
Das zweite `<main>` ist weg: das Layout stellt bereits eines, und pro
Dokument darf es nur eines geben.
Und die Seite steht jetzt in der Formensprache der Startseite statt daneben:
ein Schimmer hinter der Überschrift, Haarlinien-Karten, `lbl`-Etiketten,
gestaffelte Einblendungen. Rechts neben dem Formular steht, was danach
passiert die Frage, die jeder hat, der ein Formular vor sich sieht, und die
ein Formular allein nicht beantwortet.
--}}
<x-layouts.site title="Anfrage — CluPilot Cloud" robots="noindex">
<main class="mx-auto w-full max-w-[860px] px-5 pt-14 pb-24 sm:px-8">
<section class="relative overflow-hidden">
{{-- Derselbe warme Schimmer wie über der Startseiten-Überschrift, aus dem
Token gezeichnet statt aus einem literalen rgba(), das niemand
nachzöge. Etwas schwächer und höher: hier steht ein Formular, kein
Aufmacher. --}}
<div class="pointer-events-none absolute -top-64 left-1/2 size-[620px] -translate-x-1/2 rounded-pill bg-accent opacity-[0.07] blur-3xl" aria-hidden="true"></div>
<header class="rv">
<p class="lbl">Anfrage</p>
<h1 class="mt-3 text-[clamp(2rem,4vw,2.8rem)] font-bold leading-[1.06] tracking-[-0.035em] text-ink">
@if ($topic === 'enterprise')
Eine eigene Maschine.
@else
Sprechen Sie uns an.
@endif
</h1>
<p class="mt-4 max-w-[58ch] text-md leading-relaxed text-muted">
@if ($topic === 'enterprise')
Sagen Sie uns, wie viel Speicher Sie brauchen und wie viele Menschen damit
arbeiten. Wir rechnen Ihnen einen Server durch, auf dem nur Ihre Cloud läuft
und melden uns am selben Werktag.
@else
Schreiben Sie uns, was Sie vorhaben. Wir antworten am selben Werktag,
auf Deutsch, ohne Warteschleife.
@endif
</p>
</header>
<div class="relative mx-auto max-w-[1120px] px-5 pb-24 pt-32 sm:px-6 lg:pt-40">
@if ($sent)
{{-- Der Bestätigungszustand ersetzt das Formular, statt darüber zu
stehen: ein Formular, das nach dem Absenden weiter dasteht, lädt
zum zweiten Absenden ein. --}}
<div class="rv mt-10 rounded-xl border border-accent-border bg-accent-subtle p-7">
<h2 class="text-lg font-bold tracking-[-0.02em] text-ink">Ihre Anfrage ist da.</h2>
<p class="mt-2 max-w-[58ch] text-sm leading-relaxed text-body">
Wir haben sie bekommen und melden uns am selben Werktag. Eine Kopie geht
nicht automatisch an Sie falls Sie etwas nachreichen möchten, antworten
Sie einfach auf unsere Rückmeldung.
<header class="rv mx-auto max-w-[60ch] text-center">
<p class="lbl">Anfrage</p>
<h1 class="mt-4 text-[clamp(2.2rem,4.6vw,3.2rem)] font-bold leading-[1.04] tracking-[-0.038em] text-ink">
@if ($topic === 'enterprise')
Eine eigene Maschine.
@else
Sprechen Sie uns an.
@endif
</h1>
<p class="mx-auto mt-5 max-w-[56ch] text-lg leading-relaxed text-muted">
@if ($topic === 'enterprise')
Sagen Sie uns, wie viel Speicher Sie brauchen und wie viele Menschen damit
arbeiten. Wir rechnen Ihnen einen Server durch, auf dem nur Ihre Cloud läuft
und melden uns am selben Werktag.
@else
Schreiben Sie uns, was Sie vorhaben. Wir antworten am selben Werktag,
auf Deutsch, ohne Warteschleife.
@endif
</p>
<a href="{{ route('home') }}" class="mt-5 inline-flex items-center gap-2 text-sm font-medium text-accent-text underline-offset-4 hover:underline">
Zurück zur Übersicht
</a>
</div>
@else
<form method="POST" action="{{ route('contact.send') }}" class="rv mt-10">
@csrf
<input type="hidden" name="topic" value="{{ $topic }}">
</header>
{{-- Der Honigtopf. Aus dem Lesefluss genommen statt display:none
ein Feld, das gar nicht existiert, füllt auch kein Roboter aus,
und genau das Ausfüllen ist hier das Erkennungszeichen.
aria-hidden und tabindex=-1, damit ein Screenreader und die
Tabulatortaste es überspringen. --}}
<div class="absolute left-[-9999px] top-auto h-px w-px overflow-hidden" aria-hidden="true">
<label for="website">Website (bitte frei lassen)</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" value="">
</div>
<div class="overflow-hidden rounded-xl border border-line bg-surface shadow-xs">
@foreach ([
['company', 'Firma', 'text', false, 'organization'],
['name', 'Ihr Name', 'text', true, 'name'],
['email', 'E-Mail', 'email', true, 'email'],
['phone', 'Telefon (optional)', 'tel', false, 'tel'],
] as [$field, $label, $type, $required, $autocomplete])
<div class="border-b border-line px-6 py-5">
<label for="{{ $field }}" class="block text-sm font-medium text-ink">
{{ $label }}@if ($required)<span class="text-danger" aria-hidden="true"> *</span>@endif
</label>
<input type="{{ $type }}" id="{{ $field }}" name="{{ $field }}"
value="{{ old($field) }}" autocomplete="{{ $autocomplete }}"
@if ($required) required @endif
class="mt-2 block w-full rounded border border-line bg-surface px-3 py-2 text-sm text-ink transition focus:border-accent-border focus:outline-none">
@error($field)
<p class="mt-1.5 text-xs text-danger">{{ $message }}</p>
@enderror
</div>
@endforeach
<div class="px-6 py-5">
<label for="message" class="block text-sm font-medium text-ink">
Ihre Nachricht<span class="text-danger" aria-hidden="true"> *</span>
</label>
<textarea id="message" name="message" rows="7" required
placeholder="{{ $topic === 'enterprise'
? 'Wie viel Speicher brauchen Sie, und wie viele Menschen arbeiten damit?'
: 'Worum geht es?' }}"
class="mt-2 block w-full rounded border border-line bg-surface px-3 py-2 text-sm leading-relaxed text-ink transition focus:border-accent-border focus:outline-none">{{ old('message') }}</textarea>
@error('message')
<p class="mt-1.5 text-xs text-danger">{{ $message }}</p>
@enderror
@if ($sent)
{{-- Der Bestätigungszustand ersetzt das Formular, statt darüber zu
stehen: ein Formular, das nach dem Absenden weiter dasteht, lädt
zum zweiten Absenden ein. --}}
<div class="rv d1 mx-auto mt-12 max-w-[640px] rounded-xl border border-accent-border bg-accent-subtle p-8 text-center shadow-xs">
<span class="mx-auto grid size-11 place-items-center rounded-pill bg-surface shadow-xs">
<x-ui.icon name="check" class="size-5 text-accent-text" />
</span>
<h2 class="mt-5 text-xl font-bold tracking-[-0.02em] text-ink">Ihre Anfrage ist da.</h2>
<p class="mx-auto mt-3 max-w-[52ch] text-sm leading-relaxed text-body">
Wir haben sie bekommen und melden uns am selben Werktag. Eine Kopie geht
nicht automatisch an Sie falls Sie etwas nachreichen möchten, antworten
Sie einfach auf unsere Rückmeldung.
</p>
<div class="mt-7 flex flex-wrap justify-center gap-3">
<x-ui.button href="{{ route('home') }}" variant="ink" size="md">Zurück zur Übersicht</x-ui.button>
<x-ui.button href="{{ route('home') }}#pricing" variant="secondary" size="md">Preise ansehen</x-ui.button>
</div>
</div>
@else
{{-- Asymmetrisch: das Formular trägt die Seite, die Spalte daneben
beantwortet die Frage, die ein Formular offen lässt was
passiert, nachdem ich auf Senden gedrückt habe. Auf dem Telefon
steht sie darunter, weil dort zuerst das Feld zählt. --}}
<div class="mt-14 grid items-start gap-8 lg:grid-cols-[minmax(0,1fr)_310px]">
<form method="POST" action="{{ route('contact.send') }}" class="rv d1">
@csrf
<input type="hidden" name="topic" value="{{ $topic }}">
{{-- Der Honigtopf. Aus dem Lesefluss genommen statt display:none
ein Feld, das gar nicht existiert, füllt auch kein Roboter aus,
und genau das Ausfüllen ist hier das Erkennungszeichen.
aria-hidden und tabindex=-1, damit ein Screenreader und die
Tabulatortaste es überspringen. --}}
<div class="absolute left-[-9999px] top-auto h-px w-px overflow-hidden" aria-hidden="true">
<label for="website">Website (bitte frei lassen)</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" value="">
</div>
<div class="rounded-xl border border-line bg-surface p-6 shadow-xs sm:p-7">
{{-- Die Spaltenbreite gehört an ein eigenes Element, nicht
an das Bauteil: x-ui.input reicht mitgegebene Klassen
an das INPUT weiter, nicht an seinen Rahmen ein
`sm:col-span-2` am Bauteil landete also am Feld und
täte im Raster nichts. --}}
<div class="grid gap-5 sm:grid-cols-2">
<div class="sm:col-span-2">
<x-ui.input name="company" label="Firma" autocomplete="organization" :value="old('company')" />
</div>
<x-ui.input name="name" label="Ihr Name *" autocomplete="name" :value="old('name')" required />
<x-ui.input name="email" label="E-Mail *" type="email" autocomplete="email" :value="old('email')" required />
<div class="sm:col-span-2">
<x-ui.input name="phone" label="Telefon (optional)" type="tel" autocomplete="tel" :value="old('phone')" />
</div>
</div>
{{-- Kein eigenes Textfeld-Bauteil im Haus; die Klassen sind
deshalb aus x-ui.input übernommen, damit die beiden
Felder nicht nebeneinander unterschiedlich aussehen. --}}
<div class="mt-5 space-y-1.5">
<label for="message" class="block text-sm font-medium text-body">Ihre Nachricht *</label>
<textarea id="message" name="message" rows="7" required
@error('message') aria-invalid="true" aria-describedby="message-error" @enderror
placeholder="{{ $topic === 'enterprise'
? 'Wie viel Speicher brauchen Sie, und wie viele Menschen arbeiten damit?'
: 'Worum geht es?' }}"
class="block w-full rounded border bg-surface px-3.5 py-2.5 text-sm leading-relaxed text-ink placeholder:text-faint transition @error('message') border-danger bg-danger-bg @else border-line @enderror">{{ old('message') }}</textarea>
@error('message')
<p id="message-error" class="text-xs text-danger">{{ $message }}</p>
@enderror
</div>
<div class="mt-6 flex flex-wrap items-center justify-between gap-4 border-t border-line pt-6">
<p class="max-w-[42ch] text-xs leading-relaxed text-muted">
Wir benutzen Ihre Angaben nur, um Ihre Anfrage zu beantworten.
Näheres in der <a href="{{ route('legal.datenschutz') }}" class="underline underline-offset-2 transition-colors hover:text-body">Datenschutzerklärung</a>.
</p>
<x-ui.button type="submit" variant="ink" size="lg">Anfrage senden</x-ui.button>
</div>
</div>
</form>
<aside class="rv d2 flex flex-col gap-6">
<div class="rounded-xl border border-line bg-surface p-6 shadow-xs">
<p class="lbl">Was danach passiert</p>
<ol class="mt-4 flex flex-col gap-4">
@foreach ([
['Wir lesen mit.', 'Ihre Anfrage landet bei einem Menschen, nicht in einer Warteschlange.'],
$topic === 'enterprise'
? ['Wir rechnen durch.', 'Speicher, Nutzerzahl und Sicherung ergeben die Maschine — und den Preis.']
: ['Wir antworten.', 'Am selben Werktag, auf Deutsch, mit einer Antwort statt einer Rückfrage.'],
['Sie entscheiden.', 'Kein Vertrag, kein Rückruf-Rad — Sie bekommen etwas Schriftliches.'],
] as $i => [$head, $body])
<li class="flex gap-3.5">
<span class="mt-0.5 grid size-6 shrink-0 place-items-center rounded-pill border border-line text-[11px] font-semibold tabular-nums text-muted">{{ $i + 1 }}</span>
<span>
<span class="block text-sm font-semibold text-ink">{{ $head }}</span>
<span class="mt-1 block text-sm leading-relaxed text-muted">{{ $body }}</span>
</span>
</li>
@endforeach
</ol>
</div>
<div class="rounded-xl border border-line bg-surface p-6 shadow-xs">
<p class="lbl">Lieber direkt</p>
<a href="mailto:office&#64;clupilot.com" class="mt-3 inline-flex items-center gap-2 text-sm font-medium text-accent-text underline-offset-4 hover:underline">
<x-ui.icon name="mail" class="size-4" />office&#64;clupilot.com
</a>
<p class="mt-4 text-sm leading-relaxed text-muted">
Serverstandort EU, Betreuung auf Deutsch, AV-Vertrag inklusive.
</p>
</div>
</aside>
<div class="mt-5 flex flex-wrap items-center justify-between gap-4">
<p class="max-w-[46ch] text-xs leading-relaxed text-muted">
Wir benutzen Ihre Angaben nur, um Ihre Anfrage zu beantworten.
Näheres in der <a href="{{ route('legal.datenschutz') }}" class="underline underline-offset-2 hover:text-body">Datenschutzerklärung</a>.
</p>
<button type="submit" class="inline-flex min-h-[46px] items-center justify-center rounded-[10px] bg-ink px-[22px] text-[15px] font-semibold leading-none text-white transition hover:opacity-90">
Anfrage senden
</button>
</div>
</form>
@endif
@endif
</main>
</div>
</section>
</x-layouts.site>