260 lines
9.3 KiB
TypeScript
260 lines
9.3 KiB
TypeScript
import {AbiirEyebrow} from '~/components/AbiirEyebrow';
|
||
import type {AbiirHomeHeroCms} from '~/lib/homeHero';
|
||
import {ABIIR_WIDE_INNER_FRAME_CLASS} from '~/lib/springGlowBanner';
|
||
import heroOption04Src from '~/assets/Hero_option04.jpg';
|
||
|
||
/** Figma hero band height + marquee. */
|
||
const FIGMA_HERO_H = 978;
|
||
const FIGMA_MARQUEE_H = 68;
|
||
|
||
function toHeroImageData(
|
||
input:
|
||
| {
|
||
id?: string | null;
|
||
url: string;
|
||
altText?: string | null;
|
||
width?: number | null;
|
||
height?: number | null;
|
||
}
|
||
| null
|
||
| undefined,
|
||
) {
|
||
if (!input?.url) return null;
|
||
return {
|
||
id: input.id ?? undefined,
|
||
url: input.url,
|
||
altText: input.altText ?? '',
|
||
width: input.width ?? undefined,
|
||
height: input.height ?? undefined,
|
||
};
|
||
}
|
||
|
||
const HERO_IMAGE_STATIC = toHeroImageData({
|
||
id: 'hero-option-04',
|
||
url: heroOption04Src,
|
||
altText: 'ABiiR skincare — hero',
|
||
width: 855,
|
||
height: FIGMA_HERO_H,
|
||
});
|
||
|
||
function pickHeroImageData(cms: AbiirHomeHeroCms) {
|
||
if (cms.image?.url) {
|
||
return toHeroImageData({
|
||
id: cms.image.id,
|
||
url: cms.image.url,
|
||
altText: cms.image.alt,
|
||
width: cms.image.width,
|
||
height: cms.image.height,
|
||
});
|
||
}
|
||
return HERO_IMAGE_STATIC;
|
||
}
|
||
|
||
function isExternalHref(href: string) {
|
||
return /^https?:\/\//i.test(href);
|
||
}
|
||
|
||
function HeroCtaLink({
|
||
href,
|
||
className,
|
||
style,
|
||
children,
|
||
}: {
|
||
href: string;
|
||
className?: string;
|
||
style?: React.CSSProperties;
|
||
children: React.ReactNode;
|
||
}) {
|
||
return (
|
||
<a
|
||
href={href}
|
||
className={className}
|
||
style={style}
|
||
rel={isExternalHref(href) ? 'noreferrer' : undefined}
|
||
>
|
||
{children}
|
||
</a>
|
||
);
|
||
}
|
||
|
||
/** Figma icon564:2080 — 15×15 */
|
||
function HeroCtaArrowIcon({className}: {className?: string}) {
|
||
return (
|
||
<svg
|
||
className={className}
|
||
width={15}
|
||
height={15}
|
||
viewBox="0 0 15 15"
|
||
fill="none"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
aria-hidden
|
||
>
|
||
<path
|
||
d="M1 7.5h11M8.5 1L14 7.5 8.5 14"
|
||
stroke="currentColor"
|
||
strokeWidth={1.5}
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
/>
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
export type AbiirHomeHeroProps = {
|
||
cms: AbiirHomeHeroCms;
|
||
};
|
||
|
||
/**
|
||
* Hero: Figma `633:2179` — 1710×978 frame, image `absolute inset-0 object-cover`, copy
|
||
* `left: 206px`, `width: 637px`, vertically centered on `lg` (unpadded 1710 rail).
|
||
* Viewport gutters extend `gradientStart` / `gradientEnd` past the frame.
|
||
*/
|
||
export function AbiirHomeHero({cms}: AbiirHomeHeroProps) {
|
||
const heroBurgundy = cms.accentBurgundy;
|
||
const imageData = pickHeroImageData(cms);
|
||
const edgeLeft = cms.gradientStart;
|
||
const edgeRight = cms.gradientEnd;
|
||
|
||
/** Space outside the centered 1710px shell (matches Spring Glow full-bleed gutters). */
|
||
const viewportGutterWidth = 'max(0px, calc((100vw - min(100vw, 1710px)) / 2))';
|
||
|
||
return (
|
||
<section data-abiir-hero className="abiir-home-hero relative w-full overflow-hidden !py-0">
|
||
<div
|
||
className="relative w-full overflow-hidden lg:h-[978px]"
|
||
style={{
|
||
minHeight: `min(100svh, ${FIGMA_HERO_H}px)`,
|
||
}}
|
||
>
|
||
{/* Viewport → shell: edge colors (desktop only; mobile uses column gradient below) */}
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute inset-y-0 left-0 z-0 hidden lg:block"
|
||
style={{width: viewportGutterWidth, backgroundColor: edgeLeft}}
|
||
/>
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute inset-y-0 right-0 z-0 hidden lg:block"
|
||
style={{width: viewportGutterWidth, backgroundColor: edgeRight}}
|
||
/>
|
||
|
||
<div className={`relative z-[1] ${ABIIR_WIDE_INNER_FRAME_CLASS}`}>
|
||
{/* Full-bleed within 1710px frame (Figma has no side padding on this artboard) */}
|
||
<div
|
||
className="relative min-h-[min(100svh,978px)] w-full overflow-hidden bg-gradient-to-r from-[var(--abiir-hero-edge-l)] to-[var(--abiir-hero-edge-r)] lg:h-[978px] lg:min-h-[978px] lg:bg-transparent"
|
||
style={
|
||
{
|
||
'--abiir-hero-edge-l': edgeLeft,
|
||
'--abiir-hero-edge-r': edgeRight,
|
||
} as React.CSSProperties
|
||
}
|
||
>
|
||
{imageData ? (
|
||
<img
|
||
src={imageData.url}
|
||
alt={imageData.altText || 'ABiiR hero'}
|
||
width={imageData.width ?? 855}
|
||
height={imageData.height ?? FIGMA_HERO_H}
|
||
loading="eager"
|
||
decoding="async"
|
||
className="absolute inset-0 size-full object-cover object-center"
|
||
sizes="(min-width: 1024px) 1710px, 100vw"
|
||
/>
|
||
) : null}
|
||
</div>
|
||
|
||
{/* Readability: scrim over the left half of the shell */}
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute inset-0 z-[5] bg-gradient-to-r from-[rgba(60,6,23,0.48)] via-[rgba(60,6,23,0.12)] to-transparent sm:from-[rgba(60,6,23,0.4)] sm:via-[rgba(60,6,23,0.08)] lg:max-w-[50%]"
|
||
/>
|
||
|
||
{/* Copy — lg+: left 206px, w 637px; vertically centered in 978px frame */}
|
||
<div className="pointer-events-none absolute inset-0 z-10">
|
||
<div className="pointer-events-auto relative px-6 pb-12 pt-10 sm:px-10 lg:absolute lg:left-[206px] lg:top-1/2 lg:w-[637px] lg:max-w-[637px] lg:-translate-y-1/2 lg:px-0 lg:pb-0 lg:pt-0">
|
||
<div className="flex flex-col gap-6">
|
||
<AbiirEyebrow surface="light" className="!text-left">
|
||
{cms.eyebrow}
|
||
</AbiirEyebrow>
|
||
|
||
<h1
|
||
className="m-0 font-['Cormorant_Garamond',serif] font-light tracking-[-0.969px] text-white not-italic"
|
||
style={{
|
||
fontSize: 'clamp(2.5rem, 5.5vw, 64.6px)',
|
||
lineHeight: 1.12,
|
||
fontWeight: 300,
|
||
}}
|
||
>
|
||
<span className="block w-full">
|
||
<span className="font-light">{cms.headlineLine1Prefix}</span>
|
||
<span className="font-['Cormorant_Garamond',serif] font-light italic">
|
||
{cms.headlineLine1Italic}
|
||
</span>
|
||
</span>
|
||
<span className="block w-full">
|
||
<span className="font-light">{cms.headlineLine2Prefix}</span>
|
||
<span className="font-light not-italic" style={{color: heroBurgundy}}>
|
||
{cms.headlineLine2Accent}
|
||
</span>
|
||
</span>
|
||
</h1>
|
||
|
||
<p className="m-0 font-['Raleway',sans-serif] text-[clamp(1rem,1.29vw+0.65rem,22px)] font-medium leading-[34px] text-[rgba(250,248,243,0.8)] lg:text-[22px]">
|
||
{cms.body}
|
||
</p>
|
||
|
||
<div className="flex flex-wrap items-start gap-[21.25px]">
|
||
<HeroCtaLink
|
||
href={cms.primaryCtaHref}
|
||
className="group relative inline-flex h-[52.883px] w-full max-w-[257.586px] shrink-0 items-center justify-between pl-[36px] pr-[18px] no-underline transition-opacity duration-300 hover:opacity-[0.92]"
|
||
style={{background: heroBurgundy, color: '#fff'}}
|
||
>
|
||
<span className="font-['Raleway',sans-serif] text-[14px] font-bold uppercase leading-[19.89px] tracking-[2.3868px]">
|
||
{cms.primaryCtaLabel}
|
||
</span>
|
||
<HeroCtaArrowIcon className="shrink-0 text-white transition-transform group-hover:translate-x-0.5" />
|
||
</HeroCtaLink>
|
||
<HeroCtaLink
|
||
href={cms.secondaryCtaHref}
|
||
className="inline-flex h-[52.883px] min-w-0 shrink-0 cursor-pointer items-center justify-center border-0 border-b border-solid border-white bg-transparent pb-[17px] pt-4 no-underline transition-opacity duration-300 hover:opacity-80 lg:min-w-[96.328px]"
|
||
>
|
||
<span className="whitespace-nowrap text-left font-['Raleway',sans-serif] text-[14px] font-medium uppercase leading-[19.89px] tracking-[2.3868px] text-white">
|
||
{cms.secondaryCtaLabel}
|
||
</span>
|
||
</HeroCtaLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Marquee — full width */}
|
||
<div
|
||
className="relative z-10 w-full overflow-hidden pt-[21.25px]"
|
||
style={{background: heroBurgundy, minHeight: FIGMA_MARQUEE_H}}
|
||
>
|
||
<div className="flex h-[26px] w-full items-center overflow-hidden">
|
||
<div className="h-full min-h-0 w-full overflow-hidden opacity-75">
|
||
<div
|
||
className="animate-[hero-marquee_32s_linear_infinite] flex w-max items-center gap-[200px] whitespace-nowrap font-['Cormorant_Garamond',serif] text-[rgba(250,248,243,0.8)] font-semibold italic tracking-[1.02px]"
|
||
style={{
|
||
fontSize: 'clamp(1.4rem, calc(1.974vw + 0.7rem), 33.6px)',
|
||
lineHeight: '26px',
|
||
}}
|
||
>
|
||
{[0, 1].map((i) => (
|
||
<p key={i} className="m-0 shrink-0" aria-hidden={i === 1}>
|
||
{cms.marqueeText}
|
||
</p>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
export type {AbiirHomeHeroCms} from '~/lib/homeHero';
|