Neuaufbau: Astro 5, Anfrage statt Buchung, Kundenkarte, Design-Tokens, SEO/GEO, Security-Header

This commit is contained in:
till committed 2026-10-09 07:21:38 +02:00
1 parent 49afd192ef
commit 0aa72718a0
86 files changed
+9743 -1079

No files matched your search

+237
View File
@@ -0,0 +1,237 @@
---
import Base from '../../layouts/Base.astro';
import Icon from '../../components/Icon.astro';
import '../../styles/form.css';
import { anliegen, salon, ANFRAGE_ENDPOINT, eur } from '../../data/site';
---
<Base
title="Anfrage stellen"
description="Anfrage an das Friseur-Team VK in Tostedt für Brautstyling, Balayage, Haarverlängerung und mehr. Mit Fotos, Wunschzeitraum und Rückrufzeit. Wir melden uns telefonisch."
crumbs={[{ name: 'Anfrage', href: '/anfrage' }]}
bar="kontakt"
>
<section class="aq">
<div class="wrap aq__grid">
<div class="aq__side">
<p class="label">Anfrage</p>
<h1>Erzählen Sie uns, <em>was Sie vorhaben.</em></h1>
<p class="lead">Wir vergeben Termine nicht online, sondern im Gespräch. Mit diesen Angaben können wir vorab einschätzen, wie viel Zeit wir einplanen und wer aus dem Team am besten passt.</p>
</div>
<div class="aq__main">
<div id="f-summary" class="f__summary" role="alert" tabindex="-1" hidden>
<h2>Bitte prüfen Sie Ihre Angaben</h2>
<ul></ul>
</div>
<form id="anfrage" class="f" action={ANFRAGE_ENDPOINT} method="post" enctype="multipart/form-data" data-endpoint={ANFRAGE_ENDPOINT}>
<!-- 1 Anliegen -->
<fieldset>
<legend><span class="f__n">1</span>Worum geht es?</legend>
<div class="choices choices--2" role="radiogroup" aria-describedby="err-anliegen">
{anliegen.map((a) => (
<label class="choice">
<input type="radio" name="anliegen" value={a.value} required data-hint={a.hint} data-label={a.label} />
<span class="choice__t">{a.label}</span>
<span class="choice__h">{a.from !== null ? `ab ${eur(a.from)}` : 'Preis nach Absprache'}</span>
</label>
))}
</div>
<p class="f__err" id="err-anliegen" data-for="anliegen"></p>
</fieldset>
<!-- 2 Braut (nur bei Brautstyling) -->
<fieldset class="step-later" data-locked data-only="braut">
<legend><span class="f__n">2</span>Ihre Hochzeit <span class="f__opt nojs-only">(nur bei Brautstyling)</span></legend>
<div class="f__row3">
<div class="f__field">
<label class="f__label" for="hochzeitsdatum">Datum der Hochzeit</label>
<input type="date" id="hochzeitsdatum" name="hochzeitsdatum" />
</div>
<div class="f__field">
<label class="f__label" for="ort">Ort oder Location <span class="f__opt">(optional)</span></label>
<input type="text" id="ort" name="ort" autocomplete="off" placeholder="Name der Location oder Adresse" />
</div>
<div class="f__field">
<label class="f__label" for="personen">Personen mit Styling</label>
<input type="number" id="personen" name="personen" min="1" max="15" inputmode="numeric" placeholder="1" aria-describedby="help-personen" />
<p class="f__help" id="help-personen">Braut plus Begleitung, zum Beispiel 3</p>
</div>
</div>
</fieldset>
<!-- 3 Haar -->
<fieldset class="step-later" data-locked>
<legend><span class="f__n" data-num>2</span>Ihr Haar heute</legend>
<fieldset class="f__group">
<legend class="f__sub">Haarlänge</legend>
<div class="choices choices--inline">
<label class="choice"><input type="radio" name="haarlaenge" value="kurz" /><span class="choice__t">Kurz</span><span class="choice__h">bis Kinn</span></label>
<label class="choice"><input type="radio" name="haarlaenge" value="mittel" /><span class="choice__t">Mittel</span><span class="choice__h">bis Schulter</span></label>
<label class="choice"><input type="radio" name="haarlaenge" value="lang" /><span class="choice__t">Lang</span><span class="choice__h">über Schulter</span></label>
</div>
</fieldset>
<fieldset class="f__group">
<legend class="f__sub">Ausgangslage</legend>
<div class="choices choices--inline">
<label class="choice"><input type="radio" name="ausgangslage" value="natur" /><span class="choice__t">Naturhaar</span></label>
<label class="choice"><input type="radio" name="ausgangslage" value="gefaerbt" /><span class="choice__t">Gefärbt</span></label>
<label class="choice"><input type="radio" name="ausgangslage" value="blondiert" /><span class="choice__t">Blondiert</span></label>
<label class="choice"><input type="radio" name="ausgangslage" value="unbekannt" /><span class="choice__t">Weiß nicht</span></label>
</div>
</fieldset>
<div class="f__field">
<label class="f__label" for="wunsch">Ihr Wunsch <span class="f__opt">(optional)</span></label>
<textarea id="wunsch" name="wunsch" rows="4" maxlength="2000" aria-describedby="help-wunsch"></textarea>
<p class="f__help" id="help-wunsch">Was soll sich ändern? Was hat Ihnen beim letzten Mal nicht gefallen?</p>
</div>
<div class="f__field">
<span class="f__label" id="lbl-fotos">Fotos <span class="f__opt">(optional, bis zu 3, je höchstens 6 MB)</span></span>
<div class="drop">
<input type="file" id="fotos" name="fotos" accept="image/jpeg,image/png,image/webp,image/heic,image/heif" multiple aria-labelledby="lbl-fotos" aria-describedby="help-fotos err-fotos" />
<p class="f__help" id="help-fotos">Am besten ein Foto vom Ist-Zustand, bei Tageslicht von hinten, und eins von Ihrem Wunsch.</p>
</div>
<ul class="files" id="files" aria-live="polite"></ul>
<p class="f__err" id="err-fotos" data-for="fotos"></p>
</div>
</fieldset>
<!-- 4 Zeit -->
<fieldset class="step-later" data-locked>
<legend><span class="f__n" data-num>3</span>Wann passt es Ihnen?</legend>
<div class="f__field">
<label class="f__label" for="zeitraum">Wunschzeitraum</label>
<select id="zeitraum" name="zeitraum">
<option value="">Bitte wählen</option>
<option>So bald wie möglich</option>
<option>In den nächsten zwei Wochen</option>
<option>In diesem Monat</option>
<option>In ein bis drei Monaten</option>
<option>Später oder fester Termin</option>
</select>
</div>
<label class="check check--box">
<input type="checkbox" name="warteliste" value="on" />
<span><strong>Ich kann auch kurzfristig.</strong><br /><span class="small">Wenn ein Termin frei wird, rufen wir Sie an. So kommen Sie oft schneller dran.</span></span>
</label>
<fieldset class="f__group">
<legend class="f__sub">Waren Sie schon bei uns?</legend>
<div class="choices choices--inline">
<label class="choice"><input type="radio" name="kundenstatus" value="neu" /><span class="choice__t">Zum ersten Mal</span></label>
<label class="choice"><input type="radio" name="kundenstatus" value="bestand" /><span class="choice__t">Ja, schon öfter</span></label>
</div>
</fieldset>
</fieldset>
<!-- 5 Kontakt -->
<fieldset class="step-later" data-locked>
<legend><span class="f__n" data-num>4</span>Wie erreichen wir Sie?</legend>
<div class="f__row2">
<div class="f__field">
<label class="f__label" for="name">Name <span class="f__req" aria-hidden="true">*</span></label>
<input type="text" id="name" name="name" required autocomplete="name" maxlength="120" aria-describedby="err-name" />
<p class="f__err" id="err-name" data-for="name"></p>
</div>
<div class="f__field">
<label class="f__label" for="telefon">Telefon <span class="f__req" aria-hidden="true">*</span></label>
<input type="tel" id="telefon" name="telefon" required autocomplete="tel" inputmode="tel" pattern="[0-9 +\(\)\/\-]{6,}" maxlength="40" aria-describedby="err-telefon" />
<p class="f__err" id="err-telefon" data-for="telefon"></p>
</div>
</div>
<div class="f__field">
<label class="f__label" for="email">E-Mail <span class="f__opt">(optional)</span></label>
<input type="email" id="email" name="email" autocomplete="email" maxlength="160" aria-describedby="err-email" />
<p class="f__err" id="err-email" data-for="email"></p>
</div>
<fieldset class="f__group">
<legend class="f__sub">Wann dürfen wir zurückrufen?</legend>
<div class="choices choices--inline">
<label class="choice"><input type="radio" name="rueckruf" value="vormittags" /><span class="choice__t">Vormittags</span></label>
<label class="choice"><input type="radio" name="rueckruf" value="nachmittags" /><span class="choice__t">Nachmittags</span></label>
<label class="choice"><input type="radio" name="rueckruf" value="abends" /><span class="choice__t">Abends</span></label>
<label class="choice"><input type="radio" name="rueckruf" value="egal" checked /><span class="choice__t">Egal</span></label>
</div>
</fieldset>
<div class="pricebox" aria-live="polite">
<p class="label">Preisrahmen</p>
<p class="pricebox__v" id="pricebox-v" hidden></p>
<ul class="pricebox__all" id="pricebox-all">
{anliegen.map((a) => <li>{a.label}: {a.hint}</li>)}
</ul>
<p class="small">Richtwerte. Den genauen Preis nennen wir im Gespräch. <a href="/leistungen-preise">Alle Preise</a></p>
</div>
<label class="check">
<input type="checkbox" name="preis_ok" value="on" />
<span>Ich habe den Preisrahmen gesehen und möchte beraten werden.</span>
</label>
<label class="check">
<input type="checkbox" name="datenschutz" value="on" required aria-describedby="err-datenschutz" />
<span>Ich bin einverstanden, dass der Salon meine Angaben und Fotos speichert, um meine Anfrage zu bearbeiten. Mehr in der <a href="/datenschutz">Datenschutzerklärung</a>. <span class="f__req" aria-hidden="true">*</span></span>
</label>
<p class="f__err" id="err-datenschutz" data-for="datenschutz"></p>
<div class="hp" aria-hidden="true">
<label for="website">Website (bitte leer lassen)</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" />
</div>
<div class="f__submit">
<button type="submit" class="btn" id="f-btn"><span class="f__spin" aria-hidden="true"></span><span class="f__btn-t">Anfrage senden</span><Icon name="arrow" /></button>
<p class="f__help">* Pflichtfeld. Wir melden uns telefonisch, eine Online-Buchung gibt es bewusst nicht.</p>
<p class="f__err" id="f-net" role="alert"></p>
</div>
</fieldset>
</form>
<div id="f-done" class="done" tabindex="-1" hidden>
<span class="done__ico"><Icon name="check" /></span>
<h2 id="f-done-h">Danke, Ihre Anfrage ist da.</h2>
<p id="f-done-p">Wir rufen Sie zurück, um alles Weitere zu besprechen.</p>
<p class="small leise" id="f-done-id"></p>
<a class="more" href="/"><span>Zur Startseite</span><Icon name="arrow" /></a>
</div>
</div>
<aside class="aq__more" aria-label="So geht es weiter">
<ol class="aq__how">
<li><span>1</span>Sie schicken die Anfrage, gern mit Fotos.</li>
<li><span>2</span>Wir rufen Sie zu Ihrer Wunschzeit zurück.</li>
<li><span>3</span>Wir tragen den Termin ein und bestätigen ihn.</li>
</ol>
<div class="aq__regular">
<p class="f__label">Schon Kundin oder Kunde bei uns?</p>
<p class="small">Dann geht es am schnellsten direkt:</p>
<div class="btn-row">
<a class="btn btn--ghost" href={salon.phoneHref}><Icon name="phone" />{salon.phoneDisplay}</a>
<a class="btn btn--ghost" href={salon.whatsappHref} rel="noopener"><Icon name="whatsapp" />WhatsApp</a>
</div>
</div>
</aside>
</div>
</section>
</Base>
<script>
import { initAnfrage } from '../../scripts/anfrage';
initAnfrage();
</script>
<style>
.aq { padding-block: var(--s-7) var(--section); }
.aq__grid { display: grid; gap: var(--s-7); grid-template-areas: 'side' 'main' 'more'; }
.aq__side { grid-area: side; display: grid; gap: var(--s-5); align-content: start; }
.aq__side h1 { font-size: var(--fs-h2); }
.aq__main { grid-area: main; min-width: 0; display: grid; gap: var(--s-5); align-content: start; }
.aq__more { grid-area: more; display: grid; gap: var(--s-6); align-content: start; }
.aq__how { list-style: none; padding: 0; display: grid; gap: var(--s-3); border-top: var(--hair); padding-top: var(--s-5); }
.aq__how li { display: grid; grid-template-columns: 2rem 1fr; align-items: baseline; }
.aq__how span { font-family: var(--font-display); font-style: italic; color: var(--c-petrol); font-size: 1.25rem; }
.aq__regular { display: grid; gap: var(--s-3); padding: var(--s-5); background: var(--c-nebel-hell); border-radius: var(--r-m); }
@media (min-width: 64rem) {
.aq__grid { grid-template-columns: 0.85fr 1.15fr; grid-template-areas: 'side main' 'more main'; grid-template-rows: auto 1fr; column-gap: var(--s-9); row-gap: var(--s-6); }
}
:global(.f__row2), :global(.f__row3) { display: grid; gap: var(--s-5); }
@media (min-width: 40rem) { :global(.f__row2) { grid-template-columns: 1fr 1fr; } }
@media (min-width: 52rem) { :global(.f__row3) { grid-template-columns: 1fr 1.3fr 1fr; } }
:global(.f.is-js .nojs-only) { display: none; }
</style>