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:
Rami Bitar
2026-05-10 16:47:07 -04:00
parent 0a1fbd62bb
commit 1c034400ca
24 changed files with 747 additions and 835 deletions

View File

@@ -2,12 +2,19 @@ import { Link } from "react-router";
import { cn } from "@/lib/utils";
import { Typography } from "@/components/Typography";
export type HeroButtonVariant = "primary" | "secondary" | "outline" | "ghost";
export type HeroButton = {
label: string;
href: string;
variant: HeroButtonVariant;
};
export type HeroProps = {
tagline: string;
heading: string;
subheading: string;
primaryCta: { label: string; href: string };
secondaryCta: { label: string; href: string };
buttons: HeroButton[];
imageUrl: string;
align: "left" | "center";
height: "md" | "lg" | "full";
@@ -20,18 +27,40 @@ const heightClass: Record<HeroProps["height"], string> = {
full: "min-h-screen",
};
function buttonClass(variant: HeroButtonVariant, isDark: boolean): string {
switch (variant) {
case "primary":
return cn(
"inline-flex items-center justify-center rounded-md px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-90",
isDark ? "bg-white text-black" : "bg-foreground text-background",
);
case "secondary":
case "outline":
return cn(
"inline-flex items-center justify-center rounded-md border px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-80",
isDark ? "border-white text-white" : "border-foreground text-foreground",
);
case "ghost":
return cn(
"inline-flex items-center justify-center rounded-md px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-80",
isDark ? "text-white" : "text-foreground",
);
}
}
export function Hero({
tagline,
heading,
subheading,
primaryCta,
secondaryCta,
buttons,
imageUrl,
align,
height,
tone,
}: HeroProps) {
const isDark = tone === "dark";
const visibleButtons = (buttons ?? []).filter((b) => b?.label);
return (
<section
className={cn(
@@ -63,14 +92,15 @@ export function Hero({
)}
>
{tagline ? (
<p
<Typography
variant="caption"
className={cn(
"mb-5 text-xs uppercase tracking-[0.2em]",
"mb-5",
isDark ? "text-white/80" : "text-foreground/70",
)}
>
{tagline}
</p>
</Typography>
) : null}
<Typography variant="h1" className="max-w-3xl">
{heading}
@@ -87,35 +117,24 @@ export function Hero({
</Typography>
) : null}
<div
className={cn(
"mt-10 flex flex-wrap gap-3",
align === "center" && "justify-center",
)}
>
{primaryCta?.label ? (
<Link
to={primaryCta.href || "#"}
className={cn(
"inline-flex items-center justify-center rounded-md px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-90",
isDark ? "bg-white text-black" : "bg-foreground text-background",
)}
>
{primaryCta.label}
</Link>
) : null}
{secondaryCta?.label ? (
<Link
to={secondaryCta.href || "#"}
className={cn(
"inline-flex items-center justify-center rounded-md border px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-80",
isDark ? "border-white text-white" : "border-foreground text-foreground",
)}
>
{secondaryCta.label}
</Link>
) : null}
</div>
{visibleButtons.length > 0 ? (
<div
className={cn(
"mt-10 flex flex-wrap gap-3",
align === "center" && "justify-center",
)}
>
{visibleButtons.map((b, i) => (
<Link
key={`${b.href}-${b.label}-${i}`}
to={b.href || "#"}
className={buttonClass(b.variant, isDark)}
>
{b.label}
</Link>
))}
</div>
) : null}
</div>
</section>
);