Rebrand store as Pulse with athletic theme and shared typography
- Pulse theme tokens in app.schema.json: Archivo Black headings (weight 400) + Inter body, white bg / black pill buttons, xl radius, AI-generated athletic imagery - Add headerFontWeight theme prop so single-weight fonts (Archivo Black) load and render correctly; ThemeProvider applies font-family + weight inline so Typography works regardless of `as` element - New shared Heading component (tagline / title / subtitle with size + align + tone variants) and Typography caption variant for taglines; refactor features, faq, cta, testimonials, products-carousel, products-grid, collection-grid, recommended-products, image-gallery, newsletter-cta to use them - Hero accepts a `buttons` array (label / href / variant) replacing primaryCta/secondaryCta; cover-image component removed and existing cover blocks migrated to Hero blocks with `buttons: []` - Newsletter CTA uses shadcn Button + Input so it inherits theme radius; stacked layout fixed to keep the image - Product/collection card titles use Typography subtitle variants (font-body), heading font weight is theme-controlled - Remove orphan commerce/shop-header.tsx and commerce/shop-footer.tsx; the editor-driven navigation/footer are the live chrome Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
117
components/Heading.tsx
Normal file
117
components/Heading.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Typography, type TypographyVariant } from "@/components/Typography";
|
||||
|
||||
export type HeadingSize = "sm" | "md" | "lg" | "xl";
|
||||
export type HeadingAlign = "left" | "center";
|
||||
export type HeadingTone = "default" | "light";
|
||||
|
||||
type SizeMap = {
|
||||
title: TypographyVariant;
|
||||
subtitle: TypographyVariant;
|
||||
taglineGap: string;
|
||||
subtitleGap: string;
|
||||
};
|
||||
|
||||
const sizeMap: Record<HeadingSize, SizeMap> = {
|
||||
sm: {
|
||||
title: "h4",
|
||||
subtitle: "subtitle2",
|
||||
taglineGap: "mb-2",
|
||||
subtitleGap: "mt-2",
|
||||
},
|
||||
md: {
|
||||
title: "h3",
|
||||
subtitle: "subtitle1",
|
||||
taglineGap: "mb-3",
|
||||
subtitleGap: "mt-3",
|
||||
},
|
||||
lg: {
|
||||
title: "h2",
|
||||
subtitle: "subtitle1",
|
||||
taglineGap: "mb-3",
|
||||
subtitleGap: "mt-3",
|
||||
},
|
||||
xl: {
|
||||
title: "h1",
|
||||
subtitle: "subtitle1",
|
||||
taglineGap: "mb-4",
|
||||
subtitleGap: "mt-4",
|
||||
},
|
||||
};
|
||||
|
||||
const alignClasses: Record<HeadingAlign, string> = {
|
||||
left: "items-start text-left",
|
||||
center: "items-center text-center",
|
||||
};
|
||||
|
||||
export type HeadingProps = {
|
||||
tagline?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
size?: HeadingSize;
|
||||
align?: HeadingAlign;
|
||||
tone?: HeadingTone;
|
||||
className?: string;
|
||||
titleClassName?: string;
|
||||
subtitleClassName?: string;
|
||||
taglineClassName?: string;
|
||||
maxWidth?: string;
|
||||
};
|
||||
|
||||
export function Heading({
|
||||
tagline,
|
||||
title,
|
||||
subtitle,
|
||||
size = "lg",
|
||||
align = "left",
|
||||
tone = "default",
|
||||
className,
|
||||
titleClassName,
|
||||
subtitleClassName,
|
||||
taglineClassName,
|
||||
maxWidth,
|
||||
}: HeadingProps) {
|
||||
if (!tagline && !title && !subtitle) return null;
|
||||
const map = sizeMap[size];
|
||||
const isLight = tone === "light";
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col", alignClasses[align], maxWidth, className)}>
|
||||
{tagline ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
className={cn(
|
||||
map.taglineGap,
|
||||
isLight && "text-background/70",
|
||||
taglineClassName,
|
||||
)}
|
||||
>
|
||||
{tagline}
|
||||
</Typography>
|
||||
) : null}
|
||||
{title ? (
|
||||
<Typography
|
||||
variant={map.title}
|
||||
className={cn(isLight && "text-background", titleClassName)}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
) : null}
|
||||
{subtitle ? (
|
||||
<Typography
|
||||
variant={map.subtitle}
|
||||
className={cn(
|
||||
map.subtitleGap,
|
||||
isLight && "text-background/70",
|
||||
subtitleClassName,
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Heading;
|
||||
Reference in New Issue
Block a user