Shop Hero Editorial
Hero магазина в журнальном стиле: крупный serif-заголовок, курсивное слово, главное фото с подписью и бегущая строка.
heroecommerceeditorialtypography
Живое превью
Autumn / Winter 26N° 04
Quiet objects for everyday rituals
Clothing and homeware made slowly, in small runs, by workshops we know by name. Designed to be used for years — and to look better for it.
Made in small runs✦Natural fibres✦Repair for life✦Carbon-neutral shipping✦Free returns✦Made in small runs✦Natural fibres✦Repair for life✦Carbon-neutral shipping✦Free returns✦
Использование
Скопируйте и вставьте этот код в ваш React-компонент:
tsx
import { ShopHeroEditorial } from 'react-component-kit-ui/sections';
<ShopHeroEditorial title="Quiet *objects* for everyday rituals" primary={{ label: 'Shop now', href: '/shop/' }} />Ручная установка
Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/sections/ShopHeroEditorial.tsx + ShopHeroEditorial.css:
ShopHeroEditorial.tsx
'use client';
import ProductMedia from '../ecommerce/ProductMedia';
import './ShopHeroEditorial.css';
/** class:list → className */
function cx(...args: unknown[]): string {
const out: string[] = [];
const walk = (v: unknown) => {
if (!v) return;
if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
else if (Array.isArray(v)) v.forEach(walk);
else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
};
args.forEach(walk);
return out.join(' ');
}
interface Cta { label: string; href: string }
/**
* ShopHeroEditorial — Magazine-style shop hero: oversized serif headline with an italic accent
* word, issue/season meta, two CTAs, a tall feature image with caption and a running ticker.
*
* @param {string} kicker - Small label above the title. Default 'Autumn / Winter 26'.
* @param {string} title - Headline (wrap a word in *asterisks* to italicise it). Default 'Quiet *objects* for everyday rituals'.
* @param {string} text - Supporting paragraph.
* @param {{label: string, href: string}} primary - Main CTA.
* @param {{label: string, href: string}} secondary - Secondary CTA (optional).
* @param {{icon?: string, hue?: number, image?: string, alt?: string, caption?: string, href?: string}} media - Feature visual.
* @param {string[]} ticker - Words for the running ticker (empty hides it).
* @param {string} issue - Small index label, e.g. "N° 04".
* @param {string} variant - Layout (split|stacked). Default 'split'.
* @param {string} size - Vertical rhythm (sm|md|lg). Default 'md'.
* @param {string} className - Extra class.
*
* Accessibility: one h1; ticker is decorative (aria-hidden) and stops with reduced motion.
*/
export interface ShopHeroEditorialProps {
kicker?: string;
title?: string;
text?: string;
primary?: Cta;
secondary?: Cta;
media?: { icon?: string; hue?: number; image?: string; alt?: string; caption?: string; href?: string };
ticker?: string[];
issue?: string;
variant?: 'split' | 'stacked';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export function ShopHeroEditorial({
kicker = 'Autumn / Winter 26',
title = 'Quiet *objects* for everyday rituals',
text = 'Clothing and homeware made slowly, in small runs, by workshops we know by name. Designed to be used for years — and to look better for it.',
primary = { label: 'Shop the collection', href: '#' },
secondary = { label: 'Read the journal', href: '#' },
media = { icon: 'jacket', hue: 22, caption: 'Wool overcoat, in Rust — €320', href: '#' },
ticker = ['Made in small runs', 'Natural fibres', 'Repair for life', 'Carbon-neutral shipping', 'Free returns'],
issue = 'N° 04',
variant = 'split',
size = 'md',
className = '',
}: ShopHeroEditorialProps) {
const parts = title.split(/\*(.+?)\*/g);
const MediaTag = media.href ? 'a' : 'div';
return (
<>
<section className={cx(['sh-ed', `sh-ed--${variant}`, `sh-ed--${size}`, className])} data-rk-cb8b08="">
<div className="sh-ed__inner" data-rk-cb8b08="">
<div className="sh-ed__copy" data-rk-cb8b08="">
<p className="sh-ed__meta" data-rk-cb8b08=""><span data-rk-cb8b08="">{kicker}</span>{issue && <span data-rk-cb8b08="">{issue}</span>}</p>
<h1 className="sh-ed__title" data-rk-cb8b08="">{parts.map((p, i) => (i % 2 ? <em data-rk-cb8b08="">{p}</em> : p))}</h1>
<div className="sh-ed__foot" data-rk-cb8b08="">
{text && <p className="sh-ed__text" data-rk-cb8b08="">{text}</p>}
<div className="sh-ed__ctas" data-rk-cb8b08="">
{primary && <a className="sh-ed__btn sh-ed__btn--solid" href={primary.href} data-rk-cb8b08="">{primary.label}<span aria-hidden="true" data-rk-cb8b08="">→</span></a>}
{secondary && <a className="sh-ed__btn" href={secondary.href} data-rk-cb8b08="">{secondary.label}</a>}
</div>
</div>
</div>
<MediaTag className="sh-ed__media" href={media.href} data-rk-cb8b08="">
<ProductMedia image={media.image} alt={media.alt ?? ''} icon={media.icon} hue={media.hue} ratio="4 / 5" variant="soft" size="lg" />
{media.caption && <span className="sh-ed__caption" data-rk-cb8b08="">{media.caption}</span>}
</MediaTag>
</div>
{ticker.length > 0 && (
<div className="sh-ed__ticker" aria-hidden="true" data-rk-cb8b08="">
<div className="sh-ed__track" data-rk-cb8b08="">
{[0, 1].map(() => ticker.map((w, __i: number) => <span key={__i} data-rk-cb8b08="">{w}<i data-rk-cb8b08="">✦</i></span>))}
</div>
</div>
)}
</section>
</>
);
}
export default ShopHeroEditorial;ShopHeroEditorial.css
/* ShopHeroEditorial — ported from astro-component-kit (sections/ShopHeroEditorial.astro) */
.sh-ed:where([data-rk-cb8b08]) { container-type: inline-size; --py: clamp(2.5rem, 6vw, 5rem); color: var(--c-text-1, #fff); overflow: hidden; }
.sh-ed--sm:where([data-rk-cb8b08]) { --py: clamp(1.5rem, 4vw, 3rem); }
.sh-ed--lg:where([data-rk-cb8b08]) { --py: clamp(3.5rem, 8vw, 7rem); }
.sh-ed__inner:where([data-rk-cb8b08]) {
display: grid;
grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
gap: clamp(1.5rem, 4vw, 4rem);
align-items: end;
max-width: 1440px;
margin: 0 auto;
padding: var(--py) clamp(1rem, 3vw, 2.5rem);
}
.sh-ed--stacked:where([data-rk-cb8b08]) .sh-ed__inner:where([data-rk-cb8b08]) { grid-template-columns: 1fr; }
.sh-ed__meta:where([data-rk-cb8b08]) { display: flex; justify-content: space-between; gap: var(--sp-4, 1rem); margin: 0 0 var(--sp-6, 1.5rem); padding-bottom: var(--sp-3, 0.75rem); border-bottom: 1px solid currentColor; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; }
.sh-ed__title:where([data-rk-cb8b08]) {
margin: 0;
font-family: var(--font-display, Georgia, 'Times New Roman', serif);
font-size: clamp(3rem, 8.5vw, 8.5rem);
font-weight: 400;
line-height: 0.92;
letter-spacing: -0.045em;
text-wrap: balance;
}
.sh-ed__title:where([data-rk-cb8b08]) em:where([data-rk-cb8b08]) { font-style: italic; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
.sh-ed__foot:where([data-rk-cb8b08]) { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-6, 1.5rem); align-items: end; margin-top: var(--sp-8, 2rem); }
.sh-ed__text:where([data-rk-cb8b08]) { margin: 0; max-width: 30rem; font-size: 1rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
.sh-ed__ctas:where([data-rk-cb8b08]) { display: flex; flex-wrap: wrap; gap: var(--sp-2, 0.5rem); }
.sh-ed__btn:where([data-rk-cb8b08]) {
display: inline-flex;
align-items: center;
gap: 0.6rem;
min-height: 52px;
padding: 0 1.4rem;
border: 1px solid currentColor;
border-radius: var(--r-sm, 2px);
color: inherit;
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
text-decoration: none;
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.sh-ed__btn:hover:where([data-rk-cb8b08]) { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); }
.sh-ed__btn--solid:where([data-rk-cb8b08]) { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); border-color: var(--c-text-1, #fff); }
.sh-ed__btn--solid:hover:where([data-rk-cb8b08]) { background: var(--shop-accent, var(--c-primary-light, #d4a017)); border-color: var(--shop-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); }
.sh-ed__btn:focus-visible:where([data-rk-cb8b08]), .sh-ed__media:focus-visible:where([data-rk-cb8b08]) { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; }
.sh-ed__media:where([data-rk-cb8b08]) { position: relative; display: block; color: inherit; text-decoration: none; border-radius: var(--r-sm, 2px); overflow: hidden; }
.sh-ed__media:where([data-rk-cb8b08]) .p-media { transition: transform 1.2s cubic-bezier(0.16, 1, 0.3, 1); }
.sh-ed__media:hover:where([data-rk-cb8b08]) .p-media { transform: scale(1.03); }
.sh-ed__caption:where([data-rk-cb8b08]) { position: absolute; left: 0; right: 0; bottom: 0; padding: var(--sp-3, 0.75rem) var(--sp-4, 1rem); background: linear-gradient(transparent, rgb(var(--paper-rgb, 5 5 5) / 0.85)); font-size: 0.8rem; letter-spacing: 0.02em; }
.sh-ed__ticker:where([data-rk-cb8b08]) { border-block: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); overflow: hidden; }
.sh-ed__track:where([data-rk-cb8b08]) { display: flex; width: max-content; animation: shop-hero-editorial-sh-ed-run 40s linear infinite; }
.sh-ed__track:where([data-rk-cb8b08]) span:where([data-rk-cb8b08]) { display: inline-flex; align-items: center; gap: 1.5rem; padding: 0.9rem 0 0.9rem 1.5rem; font-family: var(--font-display, Georgia, serif); font-size: 1.15rem; font-style: italic; white-space: nowrap; }
.sh-ed__track:where([data-rk-cb8b08]) i:where([data-rk-cb8b08]) { font-style: normal; font-size: 0.7rem; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
@keyframes shop-hero-editorial-sh-ed-run { to { transform: translateX(-50%); } }
@container (max-width: 900px) {
.sh-ed__inner:where([data-rk-cb8b08]) { grid-template-columns: 1fr; }
.sh-ed__foot:where([data-rk-cb8b08]) { grid-template-columns: 1fr; }
.sh-ed__media:where([data-rk-cb8b08]) { max-width: 520px; }
}
@media (prefers-reduced-motion: reduce) {
.sh-ed__track:where([data-rk-cb8b08]) { animation: none; }
.sh-ed__media:where([data-rk-cb8b08]) .p-media { transition: none; }
}