← alle entries

cta-marquee

FC-0073Sectiesctablockv1.0.0

De afsluitende oproep van een pagina: een reusachtige, bijna onzichtbare lopende band met één zin erachter, en daarvoor gecentreerd een label, de kop, een toelichting, de knop en een voetnoot. Receptuur: marquee + knop-slot.

zelfde component — andere tokens
Laatste woord

Laten we iets maken dat blijft.

Geen presentaties, geen omwegen: één tafel, jouw vraag, en een studio die oplevert.

We nemen dit najaar twee nieuwe opdrachtgevers aan.

blocks/cta/cta-marquee.tsx
/**
 * CtaMarquee — de afsluitende oproep van een pagina: een reusachtige,
 * bijna onzichtbare lopende band met één zin erachter, en daarvoor gecentreerd
 * een label, de kop, een toelichting, de knop en een voetnoot.
 *
 * Receptuur: de band is de bestaande marquee (FC-0029), de knop komt als slot
 * (meestal button FC-0003). Alle tekst is van het project; de band herhaalt
 * de zin acht keer zodat één helft altijd breder is dan het scherm — anders
 * trekt er een gat door het beeld voor de lus opnieuw begint.
 */

"use client";

import type * as React from "react";
import { Marquee } from "../../primitives/ui/marquee";

export interface CtaMarqueeProps extends Omit<React.HTMLAttributes<HTMLElement>, "children"> {
  /** De zin die op de achtergrond voorbijtrekt. */
  phrase: string;
  /** Klein label boven de kop. */
  badge?: string;
  heading: React.ReactNode;
  /** Korte toelichting onder de kop. */
  note?: React.ReactNode;
  /** De knop (of knoppen) — meestal een Button. */
  action?: React.ReactNode;
  /** Kleine regel onder de knop. */
  footnote?: React.ReactNode;
  /** Seconden per doorloop van de band. */
  duration?: number;
}

const HERHALINGEN = 8;

export function CtaMarquee({ phrase, badge, heading, note, action, footnote, duration = 40, className, ...rest }: CtaMarqueeProps) {
  const regel = `${phrase} · `.repeat(HERHALINGEN);
  return (
    <section {...rest} className={["fp-ctam", className].filter(Boolean).join(" ")}>
      <div className="fp-ctam__band" aria-hidden="true">
        <Marquee duration={duration} fade={false} gap="0">
          <span className="fp-ctam__zin">{regel}</span>
        </Marquee>
      </div>
      <div className="fp-ctam__inhoud">
        {badge && <span className="fp-ctam__label">{badge}</span>}
        <h2 className="fp-ctam__kop">{heading}</h2>
        {note && <p className="fp-ctam__noot">{note}</p>}
        {action && <div className="fp-ctam__actie">{action}</div>}
        {footnote && <p className="fp-ctam__voetnoot">{footnote}</p>}
      </div>
    </section>
  );
}
blocks/cta/cta.css
/**
 * CTA-blocks: cta-marquee.
 * Importeren in de app-root (de registry-site doet dat in app/globals.css).
 */

/* ── CtaMarquee ────────────────────────────────────────────────────────── */

.fp-ctam {
  position: relative; overflow: hidden; width: 100%;
  padding: 4rem 1rem;
  background: var(--background); color: var(--foreground);
}
@media (min-width: 48rem) { .fp-ctam { padding: 6rem 1.5rem; } }
/* De band: één reusachtige regel, bijna onzichtbaar, achter alles. */
.fp-ctam__band {
  position: absolute; inset: 0; display: flex; align-items: center;
  pointer-events: none; user-select: none;
  color: color-mix(in srgb, var(--foreground) 6%, transparent);
}
.fp-ctam__zin {
  display: inline-block; white-space: nowrap;
  font: inherit; font-weight: 700; font-size: 22vw; line-height: 1; letter-spacing: -0.04em;
}
@media (min-width: 48rem) { .fp-ctam__zin { font-size: 16vw; } }
.fp-ctam__inhoud {
  position: relative; display: flex; flex-direction: column; align-items: center;
  max-width: 48rem; margin: 0 auto; text-align: center;
}
.fp-ctam__label {
  display: inline-flex; align-items: center; padding: 0.375rem 0.875rem;
  font-size: 0.75rem; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted-foreground); border: 1px solid var(--border); border-radius: 999px;
}
.fp-ctam__kop {
  margin: 2rem 0 0; font: inherit; font-weight: 700; letter-spacing: -0.02em; line-height: 1.05;
  font-size: clamp(2.25rem, 6vw, 4.5rem); text-wrap: balance;
}
.fp-ctam__noot { margin: 1.5rem 0 0; max-width: 36rem; font-size: 1.125rem; line-height: 1.5; color: var(--muted-foreground); text-wrap: balance; }
@media (min-width: 48rem) { .fp-ctam__noot { font-size: 1.25rem; } }
.fp-ctam__actie { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-top: 3rem; }
.fp-ctam__voetnoot { margin: 2rem 0 0; font-size: 1rem; color: var(--muted-foreground); }