AbiirSkincare/Hydrogen/web/app/components/AbiirHomeHero.tsx

320 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';