320 lines
12 KiB
TypeScript
320 lines
12 KiB
TypeScript
import {AbiirEyebrow} from '~/components/AbiirEyebrow';
|
||
import type {AbiirHomeHeroCms} from '~/lib/homeHero';
|
||
import {ABIIR_WIDE_CONTENT_SHELL_CLASS} from '~/lib/springGlowBanner';
|
||
import heroBrandSetNewSrc from '~/assets/hero-brand-set-new.png';
|
||
import heroBrandSetNewMobileSrc from '~/assets/hero-brand-set-new_mobile.png';
|
||
|
||
/** Figma hero band height + marquee. */
|
||
const FIGMA_HERO_H = 978;
|
||
const FIGMA_MARQUEE_H = 68;
|
||
/** Cropped mobile brand set (`hero-brand-set-new_mobile.png`, transparent padding removed). */
|
||
const MOBILE_HERO_IMAGE_W = 1092;
|
||
const MOBILE_HERO_IMAGE_H = 887;
|
||
|
||
/** Hero band background (left → right). */
|
||
const HERO_BG_GRADIENT_START = '#ae7f6b';
|
||
const HERO_BG_GRADIENT_END = '#cb9287';
|
||
|
||
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-brand-set-new',
|
||
url: heroBrandSetNewSrc,
|
||
altText: 'ABiiR Skincare — Hydra Serum, Moisturizer & Gua Sha',
|
||
width: 1710,
|
||
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 (legacy layout): left copy + brand set image — desktop `hero-brand-set-new.png`,
|
||
* mobile (< lg) `hero-brand-set-new_mobile.png`. CMS hero image stays single-src for all breakpoints.
|
||
* Band background: linear `#ae7f6b` → `#cb9277`. Radial `#eac5a5` glow behind the image (tight radius).
|
||
*/
|
||
export function AbiirHomeHero({cms}: AbiirHomeHeroProps) {
|
||
const heroBurgundy = cms.accentBurgundy;
|
||
const usesStaticHeroImage = !cms.image?.url;
|
||
const imageData = pickHeroImageData(cms);
|
||
const heroGradient = `linear-gradient(to right, ${HERO_BG_GRADIENT_START}, ${HERO_BG_GRADIENT_END})`;
|
||
/** Behind the photo: smaller circle, `#eac5a5` strongest at center, transparent at outer edge. */
|
||
const imageRadialGlow = `radial-gradient(circle min(32vw, 260px) at 50% 50%, rgba(234,197,165,0.68) 0%, rgba(234,197,165,0.28) 48%, rgba(234,197,165,0.06) 72%, transparent 100%)`;
|
||
|
||
return (
|
||
<section
|
||
data-abiir-hero
|
||
className="abiir-home-hero relative w-full overflow-hidden !py-0"
|
||
style={{background: heroGradient}}
|
||
>
|
||
{/* Soft light orbs (legacy Figma accents) */}
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute hidden rounded-full lg:block"
|
||
style={{
|
||
width: 'min(696px, 55vw)',
|
||
height: 'min(696px, 55vw)',
|
||
left: '8%',
|
||
top: '-18%',
|
||
background:
|
||
'radial-gradient(circle, rgba(250,248,243,0.22) 0%, transparent 68%)',
|
||
filter: 'blur(28px)',
|
||
}}
|
||
/>
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute hidden rounded-full lg:block"
|
||
style={{
|
||
width: 'min(520px, 42vw)',
|
||
height: 'min(520px, 42vw)',
|
||
right: '-8%',
|
||
top: '-12%',
|
||
background:
|
||
'radial-gradient(circle, rgba(250,248,243,0.18) 0%, transparent 70%)',
|
||
filter: 'blur(32px)',
|
||
}}
|
||
/>
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute hidden rounded-full lg:block"
|
||
style={{
|
||
width: 'min(480px, 40vw)',
|
||
height: 'min(480px, 40vw)',
|
||
right: '4%',
|
||
bottom: '-14%',
|
||
background:
|
||
'radial-gradient(circle, rgba(60,6,23,0.12) 0%, transparent 72%)',
|
||
filter: 'blur(36px)',
|
||
}}
|
||
/>
|
||
|
||
<div className={`relative ${ABIIR_WIDE_CONTENT_SHELL_CLASS}`}>
|
||
<div className="flex flex-col gap-0 lg:flex-row lg:items-stretch lg:min-h-[min(978px,92vh)]">
|
||
{/* Copy column */}
|
||
<div className="relative z-10 order-2 flex w-full flex-col justify-center pb-14 pt-6 sm:pb-16 sm:pt-8 lg:order-1 lg:w-[42%] lg:max-w-[637px] lg:py-20 xl:w-[40%]">
|
||
<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-[-2.04px] text-white not-italic"
|
||
style={{
|
||
fontSize: 'clamp(2.75rem, 9.5vw, 6.375rem)',
|
||
lineHeight: 1.08,
|
||
fontWeight: 300,
|
||
}}
|
||
>
|
||
<span className="block">
|
||
{cms.headlineLine1Prefix}
|
||
<span className="italic">{cms.headlineLine1Italic}</span>
|
||
</span>
|
||
<span className="block">
|
||
{cms.headlineLine2Prefix}
|
||
<span style={{color: heroBurgundy}}>
|
||
{cms.headlineLine2Accent}
|
||
</span>
|
||
</span>
|
||
</h1>
|
||
|
||
<p className="m-0 max-w-xl font-['Raleway',sans-serif] text-[clamp(1rem,2.2vw,22px)] font-medium leading-[34px] text-[rgba(250,248,243,0.8)]">
|
||
{cms.body}
|
||
</p>
|
||
|
||
<div className="flex flex-wrap items-start gap-[21.25px] pt-1">
|
||
<HeroCtaLink
|
||
href={cms.primaryCtaHref}
|
||
className="group relative inline-flex min-h-[52.883px] items-center justify-between gap-3 pl-[36px] pr-[28px] 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 min-h-[52.883px] 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"
|
||
>
|
||
<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>
|
||
|
||
{/* Image column — mobile: full-bleed cropped asset; lg+: cover + radial glow */}
|
||
<div className="relative z-[1] order-1 w-[calc(100%+3rem)] max-w-none -mx-6 sm:w-[calc(100%+5rem)] sm:-mx-10 lg:order-2 lg:mx-0 lg:w-full lg:max-w-none lg:flex-1">
|
||
<div className="relative w-full overflow-hidden lg:absolute lg:inset-0 lg:h-full lg:right-[-8%] xl:right-[-12%]">
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute inset-0 z-[1] hidden lg:block"
|
||
style={{background: imageRadialGlow}}
|
||
/>
|
||
{imageData ? (
|
||
usesStaticHeroImage ? (
|
||
<>
|
||
<img
|
||
src={heroBrandSetNewMobileSrc}
|
||
alt={imageData.altText || 'ABiiR Skincare — hero'}
|
||
className="relative z-[2] block w-full h-auto lg:hidden"
|
||
width={MOBILE_HERO_IMAGE_W}
|
||
height={MOBILE_HERO_IMAGE_H}
|
||
loading="eager"
|
||
decoding="async"
|
||
sizes="100vw"
|
||
/>
|
||
<img
|
||
src={imageData.url}
|
||
alt={imageData.altText || 'ABiiR Skincare — hero'}
|
||
className="relative z-[2] hidden h-full w-full object-cover object-[55%_center] lg:block"
|
||
width={imageData.width ?? 1710}
|
||
height={imageData.height ?? FIGMA_HERO_H}
|
||
loading="eager"
|
||
decoding="async"
|
||
sizes="60vw"
|
||
/>
|
||
</>
|
||
) : (
|
||
<>
|
||
<img
|
||
src={imageData.url}
|
||
alt={imageData.altText || 'ABiiR Skincare — hero'}
|
||
className="relative z-[2] block aspect-[1092/887] w-full object-cover object-center lg:hidden"
|
||
loading="eager"
|
||
decoding="async"
|
||
sizes="100vw"
|
||
/>
|
||
<img
|
||
src={imageData.url}
|
||
alt=""
|
||
aria-hidden
|
||
className="relative z-[2] hidden h-full w-full object-cover object-[55%_center] lg:block"
|
||
width={imageData.width ?? 1710}
|
||
height={imageData.height ?? FIGMA_HERO_H}
|
||
loading="eager"
|
||
decoding="async"
|
||
sizes="60vw"
|
||
/>
|
||
</>
|
||
)
|
||
) : null}
|
||
</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';
|