HeroMagazine Hero
Editorial magazine layout: verticale meta-kolom, oversized headline, smalle afbeelding rechts, scrollende ticker onderaan.
src/components/hero/HeroMagazine.astro
---
/**
* HeroMagazine
* Editorial magazine layout: grote typografie stacked, horizontale regel,
* smalle afbeeldingskolom rechts, byline links. Inspired by Vogue/NYT T Magazine.
* Puur CSS animaties — geen JS, geen externe deps.
*/
interface Props {
issue?: string;
issueNumber?: string | number;
headline: string;
deck?: string;
byline?: string;
ctaLabel?: string;
ctaHref?: string;
imageSrc: string;
imageAlt?: string;
tags?: string[];
}
const {
issue = 'Vol. 01',
issueNumber,
headline,
deck,
byline,
ctaLabel = 'Lees meer',
ctaHref = '#',
imageSrc,
imageAlt = '',
tags = [],
} = Astro.props;
---
<section class="hm" data-component="hero-magazine">
<div class="hm__inner">
<!-- Left meta column -->
<div class="hm__meta">
<div class="hm__meta-top">
<span class="hm__issue">{issue}</span>
{issueNumber && <span class="hm__issue-num">#{issueNumber}</span>}
</div>
{byline && (
<div class="hm__byline-wrap">
<div class="hm__byline-line"></div>
<p class="hm__byline">{byline}</p>
</div>
)}
{tags.length > 0 && (
<div class="hm__tags">
{tags.map(t => <span class="hm__tag">{t}</span>)}
</div>
)}
</div>
<!-- Center content -->
<div class="hm__center">
<div class="hm__rule-top"></div>
<h1 class="hm__headline" set:html={headline} />
<div class="hm__rule-bot"></div>
{deck && <p class="hm__deck">{deck}</p>}
<a href={ctaHref} class="hm__cta">
<span>{ctaLabel}</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
<!-- Right image column -->
<div class="hm__right">
<div class="hm__img-frame">
<img src={imageSrc} alt={imageAlt} class="hm__img" loading="eager" fetchpriority="high" />
</div>
<div class="hm__img-caption">
{imageAlt && <span>{imageAlt}</span>}
</div>
</div>
</div>
<!-- Bottom ticker -->
<div class="hm__ticker" aria-hidden="true">
<div class="hm__ticker-track">
{Array.from({ length: 8 }).map(() => (
<span class="hm__ticker-item">
{headline.replace(/<[^>]+>/g, '')} <span class="hm__ticker-dot">◆</span>
</span>
))}
</div>
</div>
</section>
<style>
.hm {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--color-bg, #fff);
overflow: hidden;
border-bottom: 1px solid var(--color-primary, #0a0a0a);
}
.hm__inner {
flex: 1;
display: grid;
grid-template-columns: 120px 1fr 340px;
gap: 0;
}
/* === META COLUMN === */
.hm__meta {
border-right: 1px solid var(--color-primary, #0a0a0a);
padding: 3rem 1.5rem;
display: flex;
flex-direction: column;
gap: 3rem;
animation: hm-slide-in 0.7s cubic-bezier(0.22,1,0.36,1) both;
}
.hm__meta-top { display: flex; flex-direction: column; gap: 0.25rem; }
.hm__issue {
font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-primary, #0a0a0a);
writing-mode: vertical-lr;
transform: rotate(180deg);
}
.hm__issue-num {
font-size: 0.6875rem;
letter-spacing: 0.08em;
color: var(--color-muted, #6b7280);
writing-mode: vertical-lr;
transform: rotate(180deg);
}
.hm__byline-wrap {
display: flex;
flex-direction: column;
gap: 0.75rem;
align-items: center;
}
.hm__byline-line {
width: 1px;
height: 48px;
background: var(--color-primary, #0a0a0a);
opacity: 0.3;
}
.hm__byline {
font-size: 0.6875rem;
line-height: 1.4;
color: var(--color-muted, #6b7280);
text-align: center;
writing-mode: vertical-lr;
transform: rotate(180deg);
letter-spacing: 0.04em;
}
.hm__tags {
display: flex;
flex-direction: column;
gap: 0.375rem;
margin-top: auto;
}
.hm__tag {
font-size: 0.5625rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-accent, #6366f1);
font-weight: 700;
writing-mode: vertical-lr;
transform: rotate(180deg);
}
/* === CENTER CONTENT === */
.hm__center {
padding: 4rem 3.5rem;
display: flex;
flex-direction: column;
justify-content: center;
border-right: 1px solid var(--color-primary, #0a0a0a);
}
.hm__rule-top, .hm__rule-bot {
width: 100%;
height: 2px;
background: var(--color-primary, #0a0a0a);
}
.hm__rule-top {
animation: hm-rule-grow 0.7s 0.15s cubic-bezier(0.22,1,0.36,1) both;
transform-origin: left;
}
.hm__rule-bot {
animation: hm-rule-grow 0.7s 0.5s cubic-bezier(0.22,1,0.36,1) both;
transform-origin: left;
}
.hm__headline {
font-size: clamp(3.5rem, 6.5vw, 7rem);
font-weight: 900;
line-height: 0.95;
letter-spacing: -0.05em;
color: var(--color-primary, #0a0a0a);
margin: 1.5rem 0;
animation: hm-fade-up 0.7s 0.25s cubic-bezier(0.22,1,0.36,1) both;
}
.hm__headline :global(em) {
font-style: normal;
color: var(--color-accent, #6366f1);
}
/* Outline variant */
.hm__headline :global(strong) {
-webkit-text-stroke: 2px var(--color-primary, #0a0a0a);
color: transparent;
}
.hm__deck {
font-size: 1rem;
line-height: 1.7;
color: var(--color-muted, #6b7280);
max-width: 44ch;
margin-bottom: 2.5rem;
animation: hm-fade-up 0.6s 0.45s cubic-bezier(0.22,1,0.36,1) both;
}
.hm__cta {
display: inline-flex;
align-items: center;
gap: 0.625rem;
font-size: 0.875rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--color-primary, #0a0a0a);
text-decoration: none;
border-bottom: 2px solid var(--color-primary, #0a0a0a);
padding-bottom: 0.25rem;
width: fit-content;
transition: gap 0.2s, color 0.2s, border-color 0.2s;
animation: hm-fade-up 0.6s 0.55s cubic-bezier(0.22,1,0.36,1) both;
}
.hm__cta:hover {
gap: 1rem;
color: var(--color-accent, #6366f1);
border-color: var(--color-accent, #6366f1);
}
/* === RIGHT IMAGE === */
.hm__right {
display: flex;
flex-direction: column;
animation: hm-slide-in-right 0.9s 0.1s cubic-bezier(0.22,1,0.36,1) both;
}
.hm__img-frame {
flex: 1;
overflow: hidden;
}
.hm__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.6s cubic-bezier(0.22,1,0.36,1);
}
.hm__img-frame:hover .hm__img {
transform: scale(1.03);
}
.hm__img-caption {
padding: 0.75rem 1rem;
border-top: 1px solid rgba(0,0,0,0.1);
font-size: 0.6875rem;
color: var(--color-muted, #6b7280);
letter-spacing: 0.05em;
min-height: 36px;
}
/* === TICKER === */
.hm__ticker {
border-top: 1px solid var(--color-primary, #0a0a0a);
background: var(--color-primary, #0a0a0a);
overflow: hidden;
height: 44px;
display: flex;
align-items: center;
}
.hm__ticker-track {
display: flex;
white-space: nowrap;
animation: hm-ticker 25s linear infinite;
}
.hm__ticker-item {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: rgba(255,255,255,0.7);
padding-right: 1rem;
}
.hm__ticker-dot { color: var(--color-accent, #6366f1); }
/* === KEYFRAMES === */
@keyframes hm-fade-up {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes hm-slide-in {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes hm-slide-in-right {
from { opacity: 0; transform: translateX(30px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes hm-rule-grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@keyframes hm-ticker {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
.hm__meta, .hm__headline, .hm__deck, .hm__cta,
.hm__right, .hm__rule-top, .hm__rule-bot {
animation: none;
}
.hm__ticker-track { animation: none; }
}
/* === RESPONSIVE === */
@media (max-width: 1024px) {
.hm__inner {
grid-template-columns: 80px 1fr 260px;
}
}
@media (max-width: 768px) {
.hm__inner {
grid-template-columns: 1fr;
}
.hm__meta {
border-right: none;
border-bottom: 1px solid var(--color-primary, #0a0a0a);
flex-direction: row;
align-items: center;
padding: 1rem 1.5rem;
gap: 1.5rem;
}
.hm__issue, .hm__issue-num, .hm__byline, .hm__tag {
writing-mode: horizontal-tb;
transform: none;
}
.hm__byline-wrap { flex-direction: row; }
.hm__byline-line { width: 24px; height: 1px; }
.hm__tags { flex-direction: row; margin-top: 0; }
.hm__center {
padding: 2.5rem 1.5rem;
border-right: none;
}
.hm__right {
height: 60vw;
}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
headline * | string | — | H1 — gebruik <em> voor accent, <strong> voor outline |
imageSrc * | string | — | Smalle rechter afbeelding |
issue | string | — | Volume label (verticaal in meta kolom) |
deck | string | — | Subtitel / deck text |
byline | string | — | Auteur / byline |
tags | string[] | — | Verticale tags in meta kolom |
* = verplicht