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

@@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef } from "react";
export type ThemeProps = {
headerFont?: string;
headerFontWeight?: string;
bodyFont?: string;
primaryColor?: string;
primaryForegroundColor?: string;
@@ -42,19 +43,40 @@ const shadowMap: Record<NonNullable<ThemeProps["shadow"]>, string> = {
xl: "0 20px 25px -5px rgb(0 0 0 / 0.10), 0 8px 10px -6px rgb(0 0 0 / 0.10)",
};
function googleFontsHref(headerFont?: string, bodyFont?: string): string | null {
const fonts = [headerFont, bodyFont].filter(
(f): f is string => !!f && f !== "system-ui"
);
if (fonts.length === 0) return null;
const families = Array.from(new Set(fonts))
.map((f) => `family=${encodeURIComponent(f)}:wght@400;500;600;700`)
.join("&");
return `https://fonts.googleapis.com/css2?${families}&display=swap`;
function googleFontsHref(
headerFont?: string,
bodyFont?: string,
headerFontWeight?: string,
): string | null {
const valid = (f?: string): f is string => !!f && f !== "system-ui";
const families: string[] = [];
const seen = new Set<string>();
const headerWeight = headerFontWeight || "400";
const bodyWeights = "400;500;600;700";
if (valid(headerFont)) {
seen.add(headerFont);
// Header font uses the configured weight only — avoids HTTP 400 from
// Google Fonts when a single-weight family (e.g. Archivo Black) is paired
// with a multi-weight default request.
families.push(
`family=${encodeURIComponent(headerFont)}:wght@${headerWeight}`,
);
}
if (valid(bodyFont) && !seen.has(bodyFont)) {
seen.add(bodyFont);
families.push(
`family=${encodeURIComponent(bodyFont)}:wght@${bodyWeights}`,
);
}
if (families.length === 0) return null;
return `https://fonts.googleapis.com/css2?${families.join("&")}&display=swap`;
}
export function ThemeProvider({
headerFont,
headerFontWeight,
bodyFont,
primaryColor,
primaryForegroundColor,
@@ -87,9 +109,11 @@ export function ThemeProvider({
if (maxWidth) vars["--container-max-width"] = maxWidthMap[maxWidth];
if (headerFont) vars["--font-header"] = `"${headerFont}", system-ui, sans-serif`;
if (bodyFont) vars["--font-body"] = `"${bodyFont}", system-ui, sans-serif`;
if (headerFontWeight) vars["--font-weight-header"] = headerFontWeight;
return vars;
}, [
headerFont,
headerFontWeight,
bodyFont,
primaryColor,
primaryForegroundColor,
@@ -132,8 +156,8 @@ export function ThemeProvider({
}, [cssVars]);
const fontsHref = useMemo(
() => googleFontsHref(headerFont, bodyFont),
[headerFont, bodyFont],
() => googleFontsHref(headerFont, bodyFont, headerFontWeight),
[headerFont, bodyFont, headerFontWeight],
);
// Plain CSS rules — applied directly, no Tailwind CDN runtime needed.
@@ -146,6 +170,7 @@ export function ThemeProvider({
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-header), system-ui, -apple-system, sans-serif;
font-weight: var(--font-weight-header, 600);
}
`;