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:
@@ -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);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user