ContentSplit content
Tekst links + afbeelding rechts (of omgekeerd). IntersectionObserver slide-in. Ideaal voor dienst- of feature uitleg.
src/components/content/ContentSplit.astro
---
interface Props {
eyebrow?: string;
headline: string;
body: string;
ctaLabel?: string;
ctaHref?: string;
secondaryLabel?: string;
secondaryHref?: string;
image: string;
imageAlt?: string;
reversed?: boolean;
imageFit?: 'cover' | 'contain';
}
const {
eyebrow,
headline,
body,
ctaLabel,
ctaHref = '#',
secondaryLabel,
secondaryHref = '#',
image,
imageAlt = '',
reversed = false,
imageFit = 'cover',
} = Astro.props;
---
<section class={`csp__section${reversed ? ' csp__section--reversed' : ''}`}>
<div class="csp__inner">
<div class="csp__text csp-reveal csp-reveal--left">
{eyebrow && <p class="csp__eyebrow">{eyebrow}</p>}
<h2 class="csp__headline" set:html={headline} />
<p class="csp__body">{body}</p>
{(ctaLabel || secondaryLabel) && (
<div class="csp__actions">
{ctaLabel && (
<a href={ctaHref} class="csp__cta csp__cta--primary">{ctaLabel}</a>
)}
{secondaryLabel && (
<a href={secondaryHref} class="csp__cta csp__cta--secondary">{secondaryLabel}</a>
)}
</div>
)}
</div>
<div class="csp__media csp-reveal csp-reveal--right">
<img
src={image}
alt={imageAlt}
class="csp__image"
style={`object-fit: ${imageFit};`}
loading="lazy"
decoding="async"
/>
</div>
</div>
</section>
<script>
const reveals = document.querySelectorAll<HTMLElement>('.csp-reveal');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
(entry.target as HTMLElement).classList.add('csp-reveal--visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.2 }
);
reveals.forEach((el) => observer.observe(el));
</script>
<style>
:root {
--color-primary: #0a0a0a;
--color-accent: #6366f1;
--color-bg: #fff;
--color-muted: #6b7280;
--radius: 0.5rem;
}
.csp__section {
background: var(--color-bg);
padding: clamp(3rem, 8vw, 7rem) clamp(1rem, 5vw, 2rem);
}
.csp__inner {
display: flex;
align-items: center;
gap: clamp(2rem, 5vw, 5rem);
max-width: 1200px;
margin: 0 auto;
}
.csp__section--reversed .csp__inner {
flex-direction: row-reverse;
}
/* Text side */
.csp__text {
flex: 1 1 45%;
min-width: 0;
}
.csp__eyebrow {
font-size: clamp(0.65rem, 1.2vw, 0.75rem);
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-accent);
margin: 0 0 0.75rem;
}
.csp__headline {
font-size: clamp(2rem, 3.5vw, 3rem);
font-weight: 800;
line-height: 1.15;
color: var(--color-primary);
margin: 0 0 1.25rem;
}
.csp__headline :global(em) {
font-style: italic;
color: var(--color-accent);
}
.csp__body {
font-size: clamp(1rem, 1.5vw, 1.125rem);
line-height: 1.7;
color: var(--color-muted);
margin: 0 0 2rem;
}
.csp__actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.csp__cta {
display: inline-flex;
align-items: center;
padding: 0.75rem 1.5rem;
border-radius: var(--radius);
font-size: 0.9375rem;
font-weight: 600;
text-decoration: none;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.csp__cta:hover {
opacity: 0.85;
transform: translateY(-1px);
}
.csp__cta--primary {
background: var(--color-accent);
color: #fff;
}
.csp__cta--secondary {
background: transparent;
color: var(--color-primary);
border: 1.5px solid rgba(0, 0, 0, 0.15);
}
/* Image side */
.csp__media {
flex: 1 1 55%;
min-width: 0;
}
.csp__image {
display: block;
width: 100%;
height: clamp(280px, 45vw, 560px);
object-position: center;
border-radius: var(--radius);
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.1), 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* Scroll reveal */
.csp-reveal {
opacity: 0;
transition: opacity 0.7s ease, transform 0.7s ease;
}
.csp-reveal--left {
transform: translateX(-2.5rem);
}
.csp-reveal--right {
transform: translateX(2.5rem);
transition-delay: 0.1s;
}
.csp__section--reversed .csp-reveal--left {
transform: translateX(-2.5rem);
}
.csp__section--reversed .csp-reveal--right {
transform: translateX(2.5rem);
}
.csp-reveal--visible {
opacity: 1;
transform: translateX(0);
}
/* Responsive */
@media (max-width: 768px) {
.csp__inner,
.csp__section--reversed .csp__inner {
flex-direction: column;
}
.csp__text,
.csp__media {
flex: 1 1 100%;
width: 100%;
}
.csp__media {
order: -1;
}
.csp-reveal--left,
.csp-reveal--right {
transform: translateY(1.5rem);
}
.csp-reveal--visible {
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
headline * | string | — | Sectie headline — gebruik <em> voor accent |
body * | string | — | Tekst blok (paragraph) |
image * | string | — | Afbeelding URL |
reversed | boolean | false | Afbeelding links, tekst rechts |
eyebrow | string | — | Klein label boven headline |
ctaLabel | string | — | CTA knop tekst |
* = verplicht