ContactSplitPhoto Forms
Contact sectie met full-height coverfoto links en formulier rechts. Contactinfo overlay op foto.
src/components/forms/ContactSplitPhoto.astro
---
/**
* ContactSplitPhoto
* Contact sectie — full-height foto links, formulier rechts.
*/
interface Props {
headline: string;
sub?: string;
imageSrc: string;
imageAlt?: string;
fields?: { label: string; name: string; type?: string; required?: boolean; }[];
submitLabel?: string;
phone?: string;
email?: string;
address?: string;
}
const {
headline,
sub,
imageSrc,
imageAlt = '',
fields = [
{ label: 'Naam', name: 'name', type: 'text', required: true },
{ label: 'E-mail', name: 'email', type: 'email', required: true },
{ label: 'Bericht', name: 'message', type: 'textarea', required: true },
],
submitLabel = 'Verstuur bericht',
phone,
email,
address,
} = Astro.props;
---
<section class="csp" data-component="contact-split-photo">
<div class="csp__inner">
<!-- Photo -->
<div class="csp__photo">
<img src={imageSrc} alt={imageAlt} class="csp__img" />
<div class="csp__photo-overlay">
<div class="csp__contact-info">
{phone && (
<a href={`tel:${phone}`} class="csp__info-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" opacity="0.7">
<path d="M6.6 10.8c1.4 2.8 3.8 5.1 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1-9.4 0-17-7.6-17-17 0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.3.2 2.5.6 3.6.1.3 0 .7-.2 1L6.6 10.8z"/>
</svg>
{phone}
</a>
)}
{email && (
<a href={`mailto:${email}`} class="csp__info-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" opacity="0.7">
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
{email}
</a>
)}
{address && (
<p class="csp__info-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" opacity="0.7">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
{address}
</p>
)}
</div>
</div>
</div>
<!-- Form -->
<div class="csp__form-col">
<h2 class="csp__headline">{headline}</h2>
{sub && <p class="csp__sub">{sub}</p>}
<form class="csp__form" novalidate>
{fields.map(field => (
<div class="csp__field">
<label class="csp__label" for={`csp-${field.name}`}>
{field.label}
{field.required && <span class="csp__req">*</span>}
</label>
{field.type === 'textarea' ? (
<textarea
id={`csp-${field.name}`}
name={field.name}
class="csp__input csp__textarea"
required={field.required}
rows={5}
></textarea>
) : (
<input
id={`csp-${field.name}`}
name={field.name}
type={field.type ?? 'text'}
class="csp__input"
required={field.required}
/>
)}
</div>
))}
<button type="submit" class="csp__submit">{submitLabel}</button>
</form>
</div>
</div>
</section>
<style>
.csp {
background: var(--color-bg, #fff);
overflow: hidden;
}
.csp__inner {
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 640px;
}
/* Photo */
.csp__photo {
position: relative;
overflow: hidden;
}
.csp__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.csp__photo-overlay {
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 50%);
display: flex;
align-items: flex-end;
padding: 2.5rem;
}
.csp__contact-info {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.csp__info-item {
display: flex;
align-items: center;
gap: 0.625rem;
color: rgba(255,255,255,0.85);
font-size: 0.9rem;
text-decoration: none;
transition: color 0.15s;
}
.csp__info-item:hover { color: #fff; }
/* Form col */
.csp__form-col {
padding: 4rem 3.5rem;
background: var(--color-bg, #fff);
}
.csp__headline {
font-size: clamp(1.75rem, 2.5vw, 2.5rem);
font-weight: 800;
letter-spacing: -0.03em;
line-height: 1.15;
color: var(--color-primary, #0a0a0a);
margin-bottom: 0.875rem;
}
.csp__sub {
font-size: 1rem;
color: var(--color-muted, #6b7280);
line-height: 1.6;
margin-bottom: 2rem;
}
.csp__field {
margin-bottom: 1.25rem;
}
.csp__label {
display: block;
font-size: 0.8125rem;
font-weight: 600;
color: var(--color-primary, #0a0a0a);
margin-bottom: 0.5rem;
}
.csp__req { color: var(--color-accent, #6366f1); margin-left: 0.2em; }
.csp__input {
width: 100%;
padding: 0.75rem 1rem;
border: 1.5px solid rgba(0,0,0,0.1);
border-radius: var(--radius, 0.5rem);
font-size: 0.9375rem;
font-family: inherit;
color: var(--color-primary, #0a0a0a);
background: #fff;
transition: border-color 0.15s;
outline: none;
}
.csp__input:focus {
border-color: var(--color-accent, #6366f1);
}
.csp__textarea {
resize: vertical;
min-height: 120px;
}
.csp__submit {
width: 100%;
background: var(--color-accent, #6366f1);
color: #fff;
border: none;
border-radius: var(--radius, 0.5rem);
padding: 0.9375rem;
font-size: 1rem;
font-weight: 700;
font-family: inherit;
cursor: pointer;
transition: filter 0.2s, transform 0.2s;
margin-top: 0.5rem;
}
.csp__submit:hover {
filter: brightness(1.1);
transform: translateY(-1px);
}
@media (max-width: 768px) {
.csp__inner { grid-template-columns: 1fr; }
.csp__photo { min-height: 280px; }
.csp__form-col { padding: 2.5rem 1.5rem; }
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
headline * | string | — | Formulier titel |
imageSrc * | string | — | Cover foto URL |
sub | string | — | Ondertitel |
phone | string | — | Telefoonnummer in overlay |
email | string | — | E-mail in overlay |
address | string | — | Adres in overlay |
* = verplicht