Zoeken...  ⌘K GitHub

HeroTypography Hero

Pure typografie hero — geen afbeelding. Drie regelstijlen: gevuld, outline en gradient. CSS marquee ticker. Wit, licht en donker.

/hero-typography
src/components/hero/HeroTypography.astro
---
interface Props {
  preLabel?: string;
  line1: string;
  line2?: string;
  line3?: string;
  sub?: string;
  ctaLabel?: string;
  ctaHref?: string;
  ticker?: string[];
  bg?: 'white' | 'dark' | 'light';
}

const {
  preLabel,
  line1,
  line2,
  line3,
  sub,
  ctaLabel,
  ctaHref = '#',
  ticker = [],
  bg = 'white',
} = Astro.props;

// Duplicate ticker items so the loop is seamless
const tickerItems = ticker.length > 0 ? [...ticker, ...ticker] : [];
---

<section class={`htg__root htg__root--${bg}`} aria-label="Hero">
  <div class="htg__container">

    {preLabel && (
      <div class="htg__pre-wrap" aria-hidden="false">
        <span class="htg__pre-label">{preLabel}</span>
      </div>
    )}

    <div class="htg__headline-block">
      <span class="htg__line htg__line--1">{line1}</span>
      {line2 && <span class="htg__line htg__line--2" aria-hidden="true">{line2}</span>}
      {line3 && <span class="htg__line htg__line--3">{line3}</span>}
    </div>

    <div class="htg__rule-wrap" aria-hidden="true">
      <div class="htg__rule"></div>
    </div>

    {(sub || ctaLabel) && (
      <div class="htg__footer">
        {sub && <p class="htg__sub">{sub}</p>}
        {ctaLabel && (
          <a href={ctaHref} class="htg__cta">
            {ctaLabel}
          </a>
        )}
      </div>
    )}
  </div>

  {tickerItems.length > 0 && (
    <div class="htg__ticker-track" aria-hidden="true">
      <div class="htg__ticker-inner">
        {tickerItems.map((word) => (
          <span class="htg__ticker-item">
            {word}
            <span class="htg__ticker-sep" aria-hidden="true">—</span>
          </span>
        ))}
      </div>
    </div>
  )}
</section>

<style>
  :root {
    --color-primary: #0a0a0a;
    --color-accent: #6366f1;
    --color-bg: #fff;
    --color-muted: #6b7280;
    --radius: 0.5rem;
  }

  /* ── Root & theme variants ── */
  .htg__root {
    position: relative;
    width: 100%;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
  }

  .htg__root--white {
    background: var(--color-bg);
    --htg-text: var(--color-primary);
    --htg-muted: var(--color-muted);
    --htg-stroke: var(--color-primary);
    --htg-rule: var(--color-primary);
    --htg-pre-bg: rgba(10, 10, 10, 0.07);
    --htg-pre-color: var(--color-primary);
    --htg-ticker-bg: var(--color-primary);
    --htg-ticker-color: #fff;
  }

  .htg__root--light {
    background: #f5f5f7;
    --htg-text: var(--color-primary);
    --htg-muted: var(--color-muted);
    --htg-stroke: var(--color-primary);
    --htg-rule: var(--color-primary);
    --htg-pre-bg: rgba(10, 10, 10, 0.07);
    --htg-pre-color: var(--color-primary);
    --htg-ticker-bg: var(--color-primary);
    --htg-ticker-color: #fff;
  }

  .htg__root--dark {
    background: var(--color-primary);
    --htg-text: #fff;
    --htg-muted: rgba(255, 255, 255, 0.55);
    --htg-stroke: #fff;
    --htg-rule: rgba(255, 255, 255, 0.2);
    --htg-pre-bg: rgba(255, 255, 255, 0.1);
    --htg-pre-color: rgba(255, 255, 255, 0.85);
    --htg-ticker-bg: #fff;
    --htg-ticker-color: var(--color-primary);
  }

  /* ── Container ── */
  .htg__container {
    max-width: 1440px;
    width: 100%;
    margin: 0 auto;
    padding: clamp(5rem, 10vw, 9rem) clamp(1.5rem, 6vw, 6rem) clamp(3rem, 6vw, 5rem);
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
  }

  /* ── Pre-label ── */
  @keyframes htg-fadein {
    from {
      opacity: 0;
      transform: translateY(12px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  .htg__pre-wrap {
    animation: htg-fadein 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  .htg__pre-label {
    display: inline-block;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    background: var(--htg-pre-bg);
    color: var(--htg-pre-color);
    padding: 0.35rem 0.875rem;
    border-radius: 999px;
  }

  /* ── Headline block ── */
  .htg__headline-block {
    display: flex;
    flex-direction: column;
    gap: 0;
    line-height: 1.0;
    letter-spacing: -0.04em;
  }

  .htg__line {
    display: block;
    font-weight: 900;
    font-size: clamp(4rem, 9vw, 9rem);
  }

  /* Line 1: solid fill */
  .htg__line--1 {
    color: var(--htg-text);
    animation: htg-fadein 0.7s 0.1s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  /* Line 2: outline / stroke (transparent fill) */
  .htg__line--2 {
    color: transparent;
    -webkit-text-stroke: 2px var(--htg-stroke);
    text-stroke: 2px var(--htg-stroke);
    animation: htg-fadein 0.7s 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  /* Line 3: accent gradient */
  .htg__line--3 {
    background: linear-gradient(90deg, var(--color-accent) 0%, #a855f7 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: var(--color-accent); /* fallback */
    animation: htg-fadein 0.7s 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  /* ── Horizontal rule ── */
  @keyframes htg-scalex {
    from {
      transform: scaleX(0);
      transform-origin: left;
    }
    to {
      transform: scaleX(1);
      transform-origin: left;
    }
  }

  .htg__rule-wrap {
    overflow: hidden;
    animation: htg-fadein 0.5s 0.5s both;
  }

  .htg__rule {
    height: 1px;
    background: var(--htg-rule);
    width: 100%;
    transform-origin: left;
    animation: htg-scalex 0.8s 0.55s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  /* ── Footer row ── */
  .htg__footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2rem;
    animation: htg-fadein 0.6s 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  .htg__sub {
    font-size: clamp(0.9375rem, 1.3vw, 1.125rem);
    color: var(--htg-muted);
    line-height: 1.65;
    margin: 0;
    max-width: 48ch;
    flex: 1 1 280px;
  }

  .htg__cta {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--color-accent);
    color: #fff;
    text-decoration: none;
    font-weight: 600;
    font-size: 0.9375rem;
    padding: 0.875rem 1.875rem;
    border-radius: var(--radius);
    white-space: nowrap;
    flex-shrink: 0;
    transition:
      opacity 0.2s,
      transform 0.2s;
  }

  .htg__cta:hover {
    opacity: 0.88;
    transform: translateY(-1px);
  }

  /* ── Ticker ── */
  @keyframes htg-ticker {
    from {
      transform: translateX(0);
    }
    to {
      transform: translateX(-50%);
    }
  }

  .htg__ticker-track {
    background: var(--htg-ticker-bg);
    overflow: hidden;
    padding: 0.875rem 0;
    width: 100%;
    /* pinned to bottom of section */
    margin-top: auto;
  }

  .htg__ticker-inner {
    display: flex;
    width: max-content;
    animation: htg-ticker 20s linear infinite;
    will-change: transform;
  }

  .htg__ticker-item {
    display: inline-flex;
    align-items: center;
    gap: 0.875rem;
    font-size: clamp(0.875rem, 1.1vw, 1rem);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--htg-ticker-color);
    padding: 0 1rem;
    white-space: nowrap;
  }

  .htg__ticker-sep {
    opacity: 0.35;
  }

  /* ── Mobile ── */
  @media (max-width: 640px) {
    .htg__line--2 {
      -webkit-text-stroke-width: 1.5px;
    }

    .htg__footer {
      flex-direction: column;
      align-items: flex-start;
      gap: 1.25rem;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    * {
      animation: none !important;
      transition: none !important;
    }
  }
</style>

Props

Prop Type Default Beschrijving
line1 * string Eerste regel — gevulde tekst
line2 string Tweede regel — outline/stroke stijl
line3 string Derde regel — accent gradient kleur
bg 'white' | 'light' | 'dark' 'white' Achtergrond variant
ticker string[] Scrollende woorden in marquee balk
preLabel string Klein pill-label boven headline

* = verplicht