Zoeken...  ⌘K GitHub

HeroDark Hero

Donkere hero met radiale gloed, grid achtergrond, gradient headline en optionele statistieken. Geen externe deps.

/hero-dark
src/components/hero/HeroDark.astro
---
/**
 * HeroDark
 * Dark hero met groot stacked statement, grain texture overlay,
 * animated accent orb, en code/terminal aesthetic optioneel.
 * Puur CSS animaties.
 */
interface Props {
  label?: string;
  headline: string;
  sub?: string;
  ctaLabel?: string;
  ctaHref?: string;
  secondaryLabel?: string;
  secondaryHref?: string;
  stats?: { value: string; label: string }[];
}

const {
  label,
  headline,
  sub,
  ctaLabel = 'Aan de slag',
  ctaHref = '#',
  secondaryLabel,
  secondaryHref = '#',
  stats = [],
} = Astro.props;
---

<section class="hd" data-component="hero-dark">
  <!-- Radial glow -->
  <div class="hd__glow" aria-hidden="true"></div>

  <!-- Grid lines -->
  <div class="hd__grid" aria-hidden="true"></div>

  <!-- Noise grain -->
  <div class="hd__noise" aria-hidden="true"></div>

  <div class="hd__content">
    {label && (
      <div class="hd__label-wrap">
        <span class="hd__label">{label}</span>
      </div>
    )}

    <h1 class="hd__headline" set:html={headline} />

    {sub && <p class="hd__sub">{sub}</p>}

    <div class="hd__actions">
      <a href={ctaHref} class="hd__cta">{ctaLabel}</a>
      {secondaryLabel && (
        <a href={secondaryHref} class="hd__secondary">
          {secondaryLabel}
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
            <path d="M2 7h10M8 3l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </a>
      )}
    </div>

    {stats.length > 0 && (
      <div class="hd__stats">
        {stats.map((s, i) => (
          <div class="hd__stat" style={`animation-delay:${0.6 + i * 0.1}s`}>
            <span class="hd__stat-value">{s.value}</span>
            <span class="hd__stat-label">{s.label}</span>
          </div>
        ))}
      </div>
    )}
  </div>

  <!-- Decorative corner marks -->
  <div class="hd__corner hd__corner--tl" aria-hidden="true">
    <svg width="40" height="40" viewBox="0 0 40 40" fill="none">
      <path d="M0 20H20V0" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
    </svg>
  </div>
  <div class="hd__corner hd__corner--br" aria-hidden="true">
    <svg width="40" height="40" viewBox="0 0 40 40" fill="none">
      <path d="M40 20H20V40" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
    </svg>
  </div>
</section>

<style>
  .hd {
    min-height: 100vh;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: #050508;
  }

  /* === RADIAL GLOW === */
  .hd__glow {
    position: absolute;
    width: 80vw;
    height: 80vw;
    max-width: 900px;
    max-height: 900px;
    background: radial-gradient(
      ellipse at center,
      rgba(99,102,241,0.22) 0%,
      rgba(139,92,246,0.12) 35%,
      transparent 65%
    );
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    animation: hd-glow-pulse 8s ease-in-out infinite;
  }

  /* === GRID === */
  .hd__grid {
    position: absolute;
    inset: 0;
    background-image:
      linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
      linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
    background-size: 64px 64px;
    pointer-events: none;
  }

  /* === NOISE === */
  .hd__noise {
    position: absolute;
    inset: 0;
    opacity: 0.04;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    background-size: 256px;
    pointer-events: none;
  }

  /* === CONTENT === */
  .hd__content {
    position: relative;
    z-index: 1;
    text-align: center;
    max-width: 900px;
    padding: 5rem 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  /* Label */
  .hd__label-wrap {
    margin-bottom: 2rem;
    animation: hd-fade-up 0.5s 0.1s cubic-bezier(0.22,1,0.36,1) both;
  }

  .hd__label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    border: 1px solid rgba(99,102,241,0.4);
    background: rgba(99,102,241,0.08);
    padding: 0.375rem 1rem;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    color: rgba(255,255,255,0.8);
  }

  .hd__label::before {
    content: '';
    width: 6px;
    height: 6px;
    background: var(--color-accent, #6366f1);
    border-radius: 50%;
    flex-shrink: 0;
    animation: hd-pulse 2s ease-in-out infinite;
  }

  /* Headline */
  .hd__headline {
    font-size: clamp(3rem, 7.5vw, 7.5rem);
    font-weight: 900;
    line-height: 0.95;
    letter-spacing: -0.05em;
    color: #fff;
    margin-bottom: 1.5rem;
    animation: hd-fade-up 0.7s 0.2s cubic-bezier(0.22,1,0.36,1) both;
  }

  .hd__headline :global(em) {
    font-style: normal;
    background: linear-gradient(135deg, #818cf8, #a78bfa, #c084fc);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }

  /* Outline in dark context */
  .hd__headline :global(strong) {
    -webkit-text-stroke: 1.5px rgba(255,255,255,0.4);
    -webkit-text-fill-color: transparent;
    color: transparent;
  }

  /* Sub */
  .hd__sub {
    font-size: 1.0625rem;
    line-height: 1.7;
    color: rgba(255,255,255,0.45);
    max-width: 48ch;
    margin-bottom: 2.5rem;
    animation: hd-fade-up 0.6s 0.35s cubic-bezier(0.22,1,0.36,1) both;
  }

  /* Actions */
  .hd__actions {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 3.5rem;
    animation: hd-fade-up 0.6s 0.45s cubic-bezier(0.22,1,0.36,1) both;
  }

  .hd__cta {
    display: inline-flex;
    align-items: center;
    background: var(--color-accent, #6366f1);
    color: #fff;
    padding: 1rem 2.25rem;
    border-radius: var(--radius, 0.5rem);
    font-weight: 700;
    font-size: 0.9375rem;
    text-decoration: none;
    box-shadow: 0 0 40px rgba(99,102,241,0.3);
    transition: transform 0.2s, box-shadow 0.2s;
  }

  .hd__cta:hover {
    transform: translateY(-3px);
    box-shadow: 0 0 60px rgba(99,102,241,0.45);
  }

  .hd__secondary {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.9375rem;
    font-weight: 600;
    color: rgba(255,255,255,0.55);
    text-decoration: none;
    transition: color 0.2s, gap 0.2s;
  }

  .hd__secondary:hover { color: rgba(255,255,255,0.9); gap: 0.65rem; }

  /* Stats */
  .hd__stats {
    display: flex;
    gap: 3rem;
    padding-top: 2.5rem;
    border-top: 1px solid rgba(255,255,255,0.07);
    flex-wrap: wrap;
    justify-content: center;
  }

  .hd__stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    animation: hd-fade-up 0.5s cubic-bezier(0.22,1,0.36,1) both;
  }

  .hd__stat-value {
    font-size: 2.25rem;
    font-weight: 800;
    letter-spacing: -0.04em;
    color: #fff;
    line-height: 1;
  }

  .hd__stat-label {
    font-size: 0.8125rem;
    color: rgba(255,255,255,0.4);
    letter-spacing: 0.04em;
  }

  /* === CORNERS === */
  .hd__corner {
    position: absolute;
    pointer-events: none;
  }

  .hd__corner--tl { top: 1.5rem; left: 1.5rem; }
  .hd__corner--br { bottom: 1.5rem; right: 1.5rem; }

  /* === KEYFRAMES === */
  @keyframes hd-fade-up {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  @keyframes hd-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%  { opacity: 0.5; transform: scale(1.4); }
  }

  @keyframes hd-glow-pulse {
    0%, 100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    50%  { opacity: 0.8; transform: translate(-50%, -52%) scale(1.05); }
  }

  @media (prefers-reduced-motion: reduce) {
    .hd__label-wrap, .hd__headline, .hd__sub, .hd__actions, .hd__stat { animation: none; }
    .hd__label::before { animation: none; }
    .hd__glow { animation: none; }
  }

  @media (max-width: 600px) {
    .hd__content { padding: 4rem 1.25rem; }
    .hd__actions { flex-direction: column; width: 100%; }
    .hd__cta { width: 100%; justify-content: center; }
    .hd__stats { gap: 2rem; }
  }
</style>

Props

Prop Type Default Beschrijving
headline * string H1 — gebruik <em> voor gradient kleur
label string Pill label met pulserende dot
sub string Ondertitel
stats { value: string; label: string }[] Statistieken onderaan

* = verplicht