HeroTypography Hero
Pure typografie hero — geen afbeelding. Drie regelstijlen: gevuld, outline en gradient. CSS marquee ticker. Wit, licht en donker.
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