Monorepo Site Config Plan v2
Section titled “Monorepo Site Config Plan v2”Overview
Section titled “Overview”Create a TypeScript-based configuration system that centralizes all site parameters, supports shared assets for SmartDebt brand sites, generates color variations using Tailwind’s scale, and produces a clean colors.css file with semantic naming.
Key Updates in v2:
- Tailwind color scale (50-900) instead of GIMP 0-9
- Production-build script includes color generation
- Build-time validation for assets and configuration
- Simplified site architecture: TalbotStevens.com as gateway, SmartDebtCoach.com as core hub (content, offerings)
- Single Source of Truth for offerings in SDC.com only
- Country/language support for SMART DEBT Insights
Site Architecture Strategy
Section titled “Site Architecture Strategy”TalbotStevens.com (Gateway Site)
Section titled “TalbotStevens.com (Gateway Site)”Purpose: Professional brand hub that funnels traffic to SmartDebtCoach.com
Characteristics:
- 5-7 static pages (About, Expertise, Books, Speaking, Media, Contact)
- No product/service offerings hosted here
- Legacy work referenced with CTAs to SDC.com
- Rare updates (bio changes, speaking engagements)
- Clear “Visit SMART DEBT Coach →” CTAs throughout
- English only, Canada-focused
Configuration Needs:
- Basic site identity (name, URL, description)
- Brand assets (logo, colors, typography)
- Theme system (light/dark)
- Navigation (simple, static)
- SEO metadata
- No offerings configuration needed
SmartDebtCoach.com (Content/Offerings Hub)
Section titled “SmartDebtCoach.com (Content/Offerings Hub)”Purpose: All products, services, and SMART DEBT Insights content
Characteristics:
- Static content: products, services, blog/insights
- E-commerce integration (Stripe)
- Multi-country support (Canada, USA initially)
- Multi-language ready (English now, French future)
- Active content creation and updates
- Comprehensive offerings system
Configuration Needs:
- Full site identity
- Brand assets (shared SMART DEBT logo)
- Theme system
- Complex navigation (mega menu capable)
- SEO metadata
- Offerings configuration (products, services, content)
- E-commerce configuration
- Country/language settings
Configuration File Structure
Section titled “Configuration File Structure”Primary Configuration File
Section titled “Primary Configuration File”- Location:
src/config/site.config.ts - Format: TypeScript (type-safe, IDE autocomplete)
- Structure: Strict required properties for core values, sensible defaults for optional
- Validation: Zod schema validation at build time
Configuration Properties
Section titled “Configuration Properties”1. Site Identity (Required)
Section titled “1. Site Identity (Required)”site: { name: string; // "TalbotStevens.com" | "SMART DEBT Coach" brandName: string; // "Talbot Stevens" | "SMART DEBT" entity?: string; // undefined | "Coach" | "App" | "Wealth" tagline?: string; // Optional tagline url: string; // Production URL (for Astro.site) language: string; // "en" → HTML lang attribute (default: "en") country: 'CA' | 'US'; // Primary country focus}Example - TalbotStevens.com:
site: { name: "TalbotStevens.com", brandName: "Talbot Stevens", tagline: "Financial Leverage Expert & Author", url: "https://talbotStevens.com", language: "en", country: "CA",}Example - SmartDebtCoach.com:
site: { name: "SMART DEBT Coach", brandName: "SMART DEBT", entity: "Coach", tagline: "Leverage Your Way to Financial Freedom", url: "https://smartdebtcoach.com", language: "en", country: "US", // US default, but serves CA too}2. Brand Assets (Required + Optional)
Section titled “2. Brand Assets (Required + Optional)”assets: { // Shared SmartDebt assets (used across SDC sites) shared?: { logo?: { light: string; // "../../shared/brand/smart-debt-logo-light.svg" dark: string; // "../../shared/brand/smart-debt-logo-dark.svg" }; favicon?: string; // "../../shared/brand/smart-debt-favicon.svg" smartDebtEntity?: boolean; // Use shared SmartDebt entity component };
// Site-specific assets (override shared if provided) logo: { light: string; // Path to site-specific light logo dark: string; // Path to site-specific dark logo };
favicon?: { svg?: string; // Custom favicon path (optional) generateFromLogo?: boolean; // Auto-generate from logo };
ogImage?: string; // Default Open Graph image}Asset Path Resolution Strategy:
- Shared assets: Reference from monorepo root (e.g.,
../../shared/brand/logo-light.svg) - Site assets: Reference from site’s
src/assets/brand/orpublic/brand/ - Resolution order: Site-specific takes precedence over shared
- Build-time validation: Production build verifies all asset paths exist
Example - TalbotStevens.com:
assets: { logo: { light: "src/assets/brand/ts-logo-light.svg", dark: "src/assets/brand/ts-logo-dark.svg", }, favicon: { svg: "public/brand/ts-favicon.svg", }, ogImage: "public/brand/ts-og-image.jpg",}Example - SmartDebtCoach.com:
assets: { shared: { logo: { light: "../../shared/brand/smart-debt-logo-light.svg", dark: "../../shared/brand/smart-debt-logo-dark.svg", }, favicon: "../../shared/brand/smart-debt-favicon.svg", smartDebtEntity: true, // Use shared $MART DEBT component }, logo: { // Fallback to shared, but can override if needed light: "../../shared/brand/smart-debt-logo-light.svg", dark: "../../shared/brand/smart-debt-logo-dark.svg", }, ogImage: "public/brand/sdc-og-image.jpg",}3. Brand Colors (Required)
Section titled “3. Brand Colors (Required)”Uses Tailwind Color Scale: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900
colors: { // Primary brand color (required) primary: { // Base color (500 in Tailwind scale) base: { h: number; // Hue (0-360) s: number; // Saturation (0-100) l: number; // Lightness (0-100) };
// Auto-generate Tailwind scale (50-900) from base generateScale?: boolean; // Default: true
// Manual overrides for specific shades (optional) overrides?: { 50?: { h: number; s: number; l: number } | string; 100?: { h: number; s: number; l: number } | string; // ... etc for 200-900 }; };
// Secondary color (optional for 2-color schemes) secondary?: { base: { h: number; s: number; l: number }; generateScale?: boolean; overrides?: { /* ... */ }; };
// Tertiary color (optional for 3-color schemes) tertiary?: { base: { h: number; s: number; l: number }; generateScale?: boolean; overrides?: { /* ... */ }; };
// Neutral/Grey scale (optional, defaults generated) neutral?: { generateScale?: boolean; // Default: true (generates 50-900) };
// Semantic colors (optional, with sensible defaults) semantic?: { success?: { h: number; s: number; l: number } | string; error?: { h: number; s: number; l: number } | string; warning?: { h: number; s: number; l: number } | string; info?: { h: number; s: number; l: number } | string; };
// Logo-specific colors per theme logo: { light: { bg: { h: number; s: number; l: number } | string; text: { h: number; s: number; l: number } | string; }; dark: { bg: { h: number; s: number; l: number } | string; text: { h: number; s: number; l: number } | string; }; };
// Theme-specific overrides themes: { light: { // Optional overrides for light theme primary?: { h: number; s: number; l: number }; background?: { h: number; s: number; l: number }; foreground?: { h: number; s: number; l: number }; }; dark: { // Auto-lighten by 15% for contrast, or specify manually autoAdjustContrast?: boolean; // Default: true contrastAdjustment?: number; // Default: 15 (% lighter)
// Manual overrides primary?: { h: number; s: number; l: number }; background?: { h: number; s: number; l: number }; foreground?: { h: number; s: number; l: number }; }; };}Example - TalbotStevens.com (Single Color):
colors: { primary: { base: { h: 210, s: 70, l: 45 }, // Professional blue generateScale: true, }, logo: { light: { bg: { h: 210, s: 70, l: 45 }, text: { h: 0, s: 0, l: 100 }, }, dark: { bg: { h: 210, s: 60, l: 60 }, // Lighter for dark theme text: { h: 0, s: 0, l: 10 }, }, }, themes: { dark: { autoAdjustContrast: true, contrastAdjustment: 15, }, },}Example - SmartDebtCoach.com (Two Colors):
colors: { primary: { base: { h: 217, s: 71, l: 40 }, // SMART DEBT Blue generateScale: true, }, secondary: { base: { h: 45, s: 81, l: 51 }, // Accent Gold generateScale: true, }, logo: { light: { bg: { h: 217, s: 71, l: 40 }, text: { h: 45, s: 81, l: 51 }, }, dark: { bg: { h: 217, s: 60, l: 55 }, text: { h: 45, s: 75, l: 65 }, }, }, themes: { dark: { autoAdjustContrast: true, }, },}4. Color System Generation
Section titled “4. Color System Generation”Build-Time Generation via: pnpm build:production
Process:
- Script reads
site.config.ts - Generates Tailwind color scale (50-900) from base colors using HSL lightness progression
- Applies dark theme contrast adjustments
- Creates semantic color mappings based on color count (1/2/3-color aware)
- Outputs to
src/styles/themes/colors.css - Validates all color values and contrast ratios
Tailwind Scale Generation Algorithm:
// From base color at 500, generate full scaleconst generateTailwindScale = (base: HSL) => { return { 50: adjustLightness(base, 95), // Lightest 100: adjustLightness(base, 90), 200: adjustLightness(base, 80), 300: adjustLightness(base, 70), 400: adjustLightness(base, 60), 500: base, // Base color 600: adjustLightness(base, 40), 700: adjustLightness(base, 30), 800: adjustLightness(base, 20), 900: adjustLightness(base, 10), // Darkest };};Generated CSS Structure:
/* Auto-generated from site.config.ts - DO NOT EDIT MANUALLY *//* Generated: 2025-11-12 */
:root { /* Primary color scale (Tailwind 50-900) */ --primary-50: 217 71% 95%; --primary-100: 217 71% 90%; --primary-200: 217 71% 80%; --primary-300: 217 71% 70%; --primary-400: 217 71% 60%; --primary-500: 217 71% 40%; /* Base */ --primary-600: 217 71% 40%; --primary-700: 217 71% 30%; --primary-800: 217 71% 20%; --primary-900: 217 71% 10%;
/* Secondary color scale (if 2+ colors) */ --secondary-50: 45 81% 95%; /* ... through secondary-900 */
/* Semantic mappings (auto-generated based on color count) */ --color-brand: var(--primary-500); --color-accent: var(--secondary-500, var(--primary-600)); --color-success: 142 71% 45%; --color-error: 0 72% 51%; --color-warning: 38 92% 50%; --color-info: 199 89% 48%;
/* Theme backgrounds/foregrounds */ --background: 0 0% 100%; --foreground: 0 0% 10%;}
[data-theme="dark"] { /* Auto-adjusted for contrast (15% lighter) */ --primary-500: 217 71% 55%; /* Was 40%, now 55% */ /* ... other adjustments */
--background: 0 0% 10%; --foreground: 0 0% 95%;}Semantic Mapping Strategy:
- 1-color scheme: All semantic colors use primary variations
- 2-color scheme: Brand = primary, Accent = secondary, alerts use default colors
- 3-color scheme: Brand = primary, Accent = secondary, Highlight = tertiary
5. Typography (Required)
Section titled “5. Typography (Required)”typography: { fontFamily: { primary: string; // "Inter" (default) fallback: string; // System font stack (default: comprehensive) };
fontSource: { type: 'google' | 'local' | 'none'; // Required url?: string; // Google Fonts URL or local path weights: number[]; // [400, 500, 600, 700, 900] (mapped below) };
// Named weight mappings fontWeights: { body: 400; // Regular text medium: 500; // Slightly emphasized semibold: 600; // Subheadings, important text bold: 700; // Headings, strong emphasis heavy: 900; // Display text, hero headings };
// Fluid typography from Utopia (optional, for future expansion) fluidScale?: { minViewport: number; // 320 (px) maxViewport: number; // 1500 (px) minFontSize: number; // 16 (px) maxFontSize: number; // 20 (px) typeScaleRatio: number; // 1.25 (Major Third) };}Default Fallback Font Stack:
system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji','Segoe UI Emoji', 'Segoe UI Symbol'Example:
typography: { fontFamily: { primary: "Inter", fallback: "system-ui, -apple-system, sans-serif", }, fontSource: { type: "google", url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap", weights: [400, 500, 600, 700, 900], }, fontWeights: { body: 400, medium: 500, semibold: 600, bold: 700, heavy: 900, },}6. SEO & Meta (Required + Optional)
Section titled “6. SEO & Meta (Required + Optional)”seo: { // Title handling defaultTitle: string; // "TalbotStevens.com - Financial Leverage Expert" titleTemplate?: string; // "{title} | {siteName}" (optional custom template)
// Meta descriptions defaultDescription: string; // Required: Default meta description
// Images ogImage: string; // Required: Default OG image path
// Social twitterHandle?: string; // Optional: "@talbotstevens"
// Copyright copyright: string; // Required: "{year} {siteName}. All rights reserved."
// Per-page override mechanism (in frontmatter) allowPageOverrides: boolean; // Default: true}Per-Page Overrides (Astro Frontmatter):
---const seo = { title: "About Talbot Stevens", description: "Learn about Talbot Stevens, Canada's leading financial leverage expert...", ogImage: "/images/about-og.jpg", // These override config defaults};---Platform-Specific OG Images:
// In page frontmatter or configseo: { ogImages: { default: "/og-default.jpg", // 1200x630 (Facebook, LinkedIn) twitter: "/og-twitter.jpg", // 1200x600 (Twitter/X) square: "/og-square.jpg", // 1200x1200 (WhatsApp, some social) },}7. Navigation & Content (Required + Optional)
Section titled “7. Navigation & Content (Required + Optional)”navigation: { header: { layout: 'default' | 'centered' | 'split'; // Layout variation (default: 'default') sticky: boolean; // Sticky header behavior (default: false) showLogo: boolean; // Required showBrandName: boolean; // Required entity?: string; // Optional: Override site.entity for header
// Simple menu structure menu: Array<{ label: string; url: string; external?: boolean; // Opens in new tab children?: Array<{ // Optional: Dropdown/mega menu label: string; url: string; external?: boolean; }>; }>; };
footer: { description: string; // Required: Footer description text
links: { // Primary navigation links primary: Array<{ label: string; url: string; }>;
// Legal/secondary links legal: Array<{ label: string; url: string; }>;
// Social media links (optional) social?: Array<{ label: string; url: string; icon?: string; // Icon name (lucide-react) }>; }; };}Example - TalbotStevens.com (Simple Navigation):
navigation: { header: { layout: 'default', sticky: false, showLogo: true, showBrandName: true, menu: [ { label: "Home", url: "/" }, { label: "Expertise", url: "/expertise" }, { label: "Books", url: "/books" }, { label: "Speaking", url: "/speaking" }, { label: "Media", url: "/media" }, { label: "Contact", url: "/contact" }, ], }, footer: { description: "Talbot Stevens is Canada's leading financial leverage expert and author.", links: { primary: [ { label: "Expertise", url: "/expertise" }, { label: "Speaking", url: "/speaking" }, { label: "Media", url: "/media" }, ], legal: [ { label: "Privacy Policy", url: "/legal/privacy" }, { label: "Terms of Service", url: "/legal/terms" }, ], social: [ { label: "LinkedIn", url: "https://linkedin.com/in/talbotstevens", icon: "linkedin" }, { label: "Twitter", url: "https://twitter.com/talbotstevens", icon: "twitter" }, ], }, },}Example - SmartDebtCoach.com (Complex Navigation with Mega Menu):
navigation: { header: { layout: 'default', sticky: true, showLogo: true, showBrandName: true, entity: "Coach", menu: [ { label: "Home", url: "/" }, { label: "Products", url: "/products", children: [ { label: "Debt Mastery Course", url: "/products/debt-mastery-course" }, { label: "Financial Freedom Guide", url: "/products/financial-freedom-guide" }, { label: "Leverage Calculator", url: "/products/leverage-calculator" }, ], }, { label: "Services", url: "/services", children: [ { label: "Group Coaching", url: "/services/group-coaching" }, { label: "One-on-One Coaching", url: "/services/one-on-one" }, { label: "Corporate Training", url: "/services/corporate" }, ], }, { label: "Insights", url: "/insights" }, { label: "About", url: "/about" }, { label: "Contact", url: "/contact" }, ], }, footer: { description: "SMART DEBT Coach - Leverage your way to financial freedom.", links: { primary: [ { label: "Products", url: "/products" }, { label: "Services", url: "/services" }, { label: "Insights", url: "/insights" }, { label: "About", url: "/about" }, ], legal: [ { label: "Privacy Policy", url: "/legal/privacy" }, { label: "Terms of Service", url: "/legal/terms" }, { label: "Refund Policy", url: "/legal/refunds" }, ], social: [ { label: "Facebook", url: "https://facebook.com/smartdebtcoach", icon: "facebook" }, { label: "Twitter", url: "https://twitter.com/smartdebtcoach", icon: "twitter" }, { label: "LinkedIn", url: "https://linkedin.com/company/smart-debt-coach", icon: "linkedin" }, { label: "YouTube", url: "https://youtube.com/@smartdebtcoach", icon: "youtube" }, ], }, },}8. Theme Configuration (Required)
Section titled “8. Theme Configuration (Required)”theme: { defaultTheme: 'light' | 'dark' | 'system'; // Required (default: 'system') allowToggle: boolean; // Allow user to toggle theme (default: true)
storageKeys: { theme: string; // Default: 'theme-preference' };}Note: Color themes are NOT dynamic user-selectable. The config defines the site’s brand colors at build time. Users can only toggle light/dark mode.
9. Offerings Configuration (SDC.com Only)
Section titled “9. Offerings Configuration (SDC.com Only)”TalbotStevens.com: No offerings configuration needed (gateway site only)
SmartDebtCoach.com: Full offerings system
offerings: { // Products products: { enabled: boolean; // true basePath: string; // "/products" (URL base) dataPath: string; // "src/content/products" (file location)
// Country/language support countries: ('CA' | 'US')[]; // ['CA', 'US'] languages: ('en' | 'fr')[]; // ['en'] (future: ['en', 'fr']) defaultCountry: 'CA' | 'US'; defaultLanguage: 'en' | 'fr'; };
// Services services: { enabled: boolean; // true basePath: string; // "/services" dataPath: string; // "src/content/services" countries: ('CA' | 'US')[]; languages: ('en' | 'fr')[]; defaultCountry: 'CA' | 'US'; defaultLanguage: 'en' | 'fr'; };
// Content (SMART DEBT Insights) content: { enabled: boolean; // true basePath: string; // "/insights" dataPath: string; // "src/content/insights"
// Multi-country, multi-language countries: ('CA' | 'US')[]; languages: ('en' | 'fr')[]; defaultCountry: 'CA' | 'US'; defaultLanguage: 'en' | 'fr';
// Content features categories: string[]; // ["Debt Strategy", "Leverage", "Investing", ...] tags: string[]; // Auto-generated from content relatedArticles: boolean; // Show related articles (default: true) };}Content Structure (Astro Content Collections):
sites/SmartDebtCoach/└── src/ └── content/ ├── products/ │ ├── debt-mastery-course/ │ │ ├── en-CA.md # Canadian English │ │ ├── en-US.md # American English │ │ ├── fr-CA.md # French Canadian (future) │ │ └── meta.json # Product metadata (pricing, Stripe IDs) │ └── ... │ ├── services/ │ ├── group-coaching/ │ │ ├── en-CA.md │ │ ├── en-US.md │ │ └── meta.json │ └── ... │ └── insights/ ├── understanding-good-debt/ │ ├── en-CA.md │ ├── en-US.md │ ├── fr-CA.md # Future │ └── meta.json # Article metadata └── ...Content Frontmatter Example:
---title: "Understanding Good Debt vs. Bad Debt"description: "Learn how to distinguish between debt that builds wealth and debt that drains it."publishDate: 2025-01-15updatedDate: 2025-02-10author: "Talbot Stevens"category: "Debt Strategy"tags: ["good debt", "leverage", "investing"]country: "CA"language: "en"featured: trueseo: metaTitle: "Good Debt vs Bad Debt: A Canadian's Guide | SMART DEBT Coach" metaDescription: "Discover how Canadian investors use good debt to accelerate wealth building while avoiding the traps of bad debt." ogImage: "/images/insights/good-debt-og.jpg"---
Content goes here...Country/Language Detection Logic:
// Detect user's country/language preference// Priority: URL param > Cookie > IP geolocation > Browser language > Default
const detectUserPreference = () => { // 1. Check URL: /insights/article?country=CA&lang=en const urlParams = new URLSearchParams(window.location.search); const urlCountry = urlParams.get('country'); const urlLang = urlParams.get('lang');
// 2. Check cookie const cookieCountry = getCookie('preferred-country'); const cookieLang = getCookie('preferred-language');
// 3. IP geolocation (Cloudflare provides this) const ipCountry = request.cf?.country; // 'CA' or 'US'
// 4. Browser language const browserLang = navigator.language.split('-')[0]; // 'en' or 'fr'
// 5. Default from config const defaultCountry = siteConfig.offerings.content.defaultCountry; const defaultLang = siteConfig.offerings.content.defaultLanguage;
return { country: urlCountry || cookieCountry || ipCountry || defaultCountry, language: urlLang || cookieLang || browserLang || defaultLang, };};10. E-Commerce Configuration (SDC.com Only)
Section titled “10. E-Commerce Configuration (SDC.com Only)”commerce: { provider: 'stripe'; // Only Stripe for now
// Stripe configuration stripe: { publicKey: string; // From env: PUBLIC_STRIPE_KEY
// Multi-country support (future) accounts?: { CA: { publicKey: string; }; // Canadian Stripe account US: { publicKey: string; }; // US Stripe account }; };
// Currency handling defaultCurrency: 'CAD' | 'USD'; currencyByCountry: { CA: 'CAD'; US: 'USD'; };
// Tax handling taxIncluded: boolean; // Whether prices include tax (Canada: often yes, US: often no)
// Checkout settings checkout: { successUrl: string; // "/checkout/success" cancelUrl: string; // "/checkout/cancel" };}11. Analytics & Integrations (Optional)
Section titled “11. Analytics & Integrations (Optional)”analytics?: { // Plausible Analytics (privacy-friendly) plausible?: { domain: string; // "smartdebtcoach.com" customDomain?: string; // Custom Plausible domain (if self-hosted) };
// Google Tag Manager (if needed) gtm?: { id: string; // "GTM-XXXXXXX" };};
integrations?: { // Form handling forms?: { provider: 'web3forms' | 'formspree'; apiKey: string; // From env honeypot: boolean; // Default: true features: { captcha?: 'recaptcha' | 'hcaptcha' | 'turnstile'; webhooks?: string[]; // n8n webhook URLs }; };
// Authentication (future) auth?: { provider: 'supabase' | 'espocrm'; config: Record<string, any>; };};12. Build Configuration (New in v2)
Section titled “12. Build Configuration (New in v2)”build: { // Color generation colors: { regenerate: 'on-change' | 'always'; // Default: 'on-change' outputPath: string; // Default: 'src/styles/themes/colors.css' };
// Asset validation assets: { validateOnBuild: boolean; // Default: true (production builds) failOnMissing: boolean; // Default: true (stops build if assets missing) };
// Image optimization images: { formats: ('webp' | 'avif')[]; // Default: ['webp', 'avif'] quality: number; // Default: 80 };}Complete Configuration Examples
Section titled “Complete Configuration Examples”TalbotStevens.com Configuration
Section titled “TalbotStevens.com Configuration”import { defineConfig } from './site.config.schema';
export default defineConfig({ site: { name: "TalbotStevens.com", brandName: "Talbot Stevens", tagline: "Financial Leverage Expert & Author", url: "https://talbotStevens.com", language: "en", country: "CA", },
assets: { logo: { light: "src/assets/brand/ts-logo-light.svg", dark: "src/assets/brand/ts-logo-dark.svg", }, favicon: { svg: "public/brand/ts-favicon.svg", }, ogImage: "public/brand/ts-og-image.jpg", },
colors: { primary: { base: { h: 210, s: 70, l: 45 }, generateScale: true, }, logo: { light: { bg: { h: 210, s: 70, l: 45 }, text: { h: 0, s: 0, l: 100 }, }, dark: { bg: { h: 210, s: 60, l: 60 }, text: { h: 0, s: 0, l: 10 }, }, }, themes: { dark: { autoAdjustContrast: true, contrastAdjustment: 15, }, }, },
typography: { fontFamily: { primary: "Inter", fallback: "system-ui, -apple-system, sans-serif", }, fontSource: { type: "google", url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap", weights: [400, 500, 600, 700, 900], }, fontWeights: { body: 400, medium: 500, semibold: 600, bold: 700, heavy: 900, }, },
seo: { defaultTitle: "Talbot Stevens - Financial Leverage Expert & Author", titleTemplate: "{title} | Talbot Stevens", defaultDescription: "Talbot Stevens is Canada's leading financial leverage expert, helping Canadians achieve financial freedom through smart debt strategies.", ogImage: "public/brand/ts-og-image.jpg", twitterHandle: "@talbotstevens", copyright: "{year} Talbot Stevens. All rights reserved.", allowPageOverrides: true, },
navigation: { header: { layout: "default", sticky: false, showLogo: true, showBrandName: true, menu: [ { label: "Home", url: "/" }, { label: "Expertise", url: "/expertise" }, { label: "Books", url: "/books" }, { label: "Speaking", url: "/speaking" }, { label: "Media", url: "/media" }, { label: "Contact", url: "/contact" }, ], }, footer: { description: "Talbot Stevens is Canada's leading financial leverage expert and author.", links: { primary: [ { label: "Expertise", url: "/expertise" }, { label: "Speaking", url: "/speaking" }, { label: "Media", url: "/media" }, ], legal: [ { label: "Privacy Policy", url: "/legal/privacy" }, { label: "Terms of Service", url: "/legal/terms" }, ], social: [ { label: "LinkedIn", url: "https://linkedin.com/in/talbotstevens", icon: "linkedin" }, { label: "Twitter", url: "https://twitter.com/talbotstevens", icon: "twitter" }, ], }, }, },
theme: { defaultTheme: "system", allowToggle: true, storageKeys: { theme: "ts-theme-preference", }, },
// No offerings configuration (gateway site)
analytics: { plausible: { domain: "talbotStevens.com", }, },
integrations: { forms: { provider: "web3forms", apiKey: import.meta.env.PUBLIC_WEB3FORMS_KEY, honeypot: true, }, },
build: { colors: { regenerate: "on-change", outputPath: "src/styles/themes/colors.css", }, assets: { validateOnBuild: true, failOnMissing: true, }, images: { formats: ["webp", "avif"], quality: 85, }, },});SmartDebtCoach.com Configuration
Section titled “SmartDebtCoach.com Configuration”import { defineConfig } from './site.config.schema';
export default defineConfig({ site: { name: "SMART DEBT Coach", brandName: "SMART DEBT", entity: "Coach", tagline: "Leverage Your Way to Financial Freedom", url: "https://smartdebtcoach.com", language: "en", country: "US", },
assets: { shared: { logo: { light: "../../shared/brand/smart-debt-logo-light.svg", dark: "../../shared/brand/smart-debt-logo-dark.svg", }, favicon: "../../shared/brand/smart-debt-favicon.svg", smartDebtEntity: true, }, logo: { light: "../../shared/brand/smart-debt-logo-light.svg", dark: "../../shared/brand/smart-debt-logo-dark.svg", }, ogImage: "public/brand/sdc-og-image.jpg", },
colors: { primary: { base: { h: 217, s: 71, l: 40 }, generateScale: true, }, secondary: { base: { h: 45, s: 81, l: 51 }, generateScale: true, }, logo: { light: { bg: { h: 217, s: 71, l: 40 }, text: { h: 45, s: 81, l: 51 }, }, dark: { bg: { h: 217, s: 60, l: 55 }, text: { h: 45, s: 75, l: 65 }, }, }, themes: { dark: { autoAdjustContrast: true, contrastAdjustment: 15, }, }, },
typography: { fontFamily: { primary: "Inter", fallback: "system-ui, -apple-system, sans-serif", }, fontSource: { type: "google", url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap", weights: [400, 500, 600, 700, 900], }, fontWeights: { body: 400, medium: 500, semibold: 600, bold: 700, heavy: 900, }, },
seo: { defaultTitle: "SMART DEBT Coach - Leverage Your Way to Financial Freedom", titleTemplate: "{title} | SMART DEBT Coach", defaultDescription: "Learn how to leverage good debt to build wealth faster with proven strategies from financial leverage expert Talbot Stevens.", ogImage: "public/brand/sdc-og-image.jpg", twitterHandle: "@smartdebtcoach", copyright: "{year} SMART DEBT Coach. All rights reserved.", allowPageOverrides: true, },
navigation: { header: { layout: "default", sticky: true, showLogo: true, showBrandName: true, entity: "Coach", menu: [ { label: "Home", url: "/" }, { label: "Products", url: "/products", children: [ { label: "Debt Mastery Course", url: "/products/debt-mastery-course" }, { label: "Financial Freedom Guide", url: "/products/financial-freedom-guide" }, { label: "Leverage Calculator", url: "/products/leverage-calculator" }, ], }, { label: "Services", url: "/services", children: [ { label: "Group Coaching", url: "/services/group-coaching" }, { label: "One-on-One Coaching", url: "/services/one-on-one" }, { label: "Corporate Training", url: "/services/corporate" }, ], }, { label: "Insights", url: "/insights" }, { label: "About", url: "/about" }, { label: "Contact", url: "/contact" }, ], }, footer: { description: "SMART DEBT Coach helps you leverage your way to financial freedom with proven debt strategies.", links: { primary: [ { label: "Products", url: "/products" }, { label: "Services", url: "/services" }, { label: "Insights", url: "/insights" }, { label: "About", url: "/about" }, ], legal: [ { label: "Privacy Policy", url: "/legal/privacy" }, { label: "Terms of Service", url: "/legal/terms" }, { label: "Refund Policy", url: "/legal/refunds" }, ], social: [ { label: "Facebook", url: "https://facebook.com/smartdebtcoach", icon: "facebook" }, { label: "Twitter", url: "https://twitter.com/smartdebtcoach", icon: "twitter" }, { label: "LinkedIn", url: "https://linkedin.com/company/smart-debt-coach", icon: "linkedin" }, { label: "YouTube", url: "https://youtube.com/@smartdebtcoach", icon: "youtube" }, ], }, }, },
theme: { defaultTheme: "system", allowToggle: true, storageKeys: { theme: "sdc-theme-preference", }, },
offerings: { products: { enabled: true, basePath: "/products", dataPath: "src/content/products", countries: ["CA", "US"], languages: ["en"], defaultCountry: "US", defaultLanguage: "en", }, services: { enabled: true, basePath: "/services", dataPath: "src/content/services", countries: ["CA", "US"], languages: ["en"], defaultCountry: "US", defaultLanguage: "en", }, content: { enabled: true, basePath: "/insights", dataPath: "src/content/insights", countries: ["CA", "US"], languages: ["en"], defaultCountry: "US", defaultLanguage: "en", categories: ["Debt Strategy", "Leverage", "Investing", "Tax Strategies", "Real Estate"], relatedArticles: true, }, },
commerce: { provider: "stripe", stripe: { publicKey: import.meta.env.PUBLIC_STRIPE_KEY, }, defaultCurrency: "USD", currencyByCountry: { CA: "CAD", US: "USD", }, taxIncluded: false, checkout: { successUrl: "/checkout/success", cancelUrl: "/checkout/cancel", }, },
analytics: { plausible: { domain: "smartdebtcoach.com", }, },
integrations: { forms: { provider: "web3forms", apiKey: import.meta.env.PUBLIC_WEB3FORMS_KEY, honeypot: true, features: { captcha: "turnstile", webhooks: [import.meta.env.N8N_WEBHOOK_URL], }, }, },
build: { colors: { regenerate: "on-change", outputPath: "src/styles/themes/colors.css", }, assets: { validateOnBuild: true, failOnMissing: true, }, images: { formats: ["webp", "avif"], quality: 80, }, },});Implementation Strategy
Section titled “Implementation Strategy”Phase 1: Configuration Infrastructure (Week 1)
Section titled “Phase 1: Configuration Infrastructure (Week 1)”Tasks:
- Create TypeScript schema with Zod validation
- Create
site.config.schema.tswith all interfaces - Create
site.config.default.tswith documented template - Create
src/lib/config-loader.ts- Load and validate config - Create
src/lib/color-utils.ts- HSL/hex conversion, Tailwind scale generation
Files to Create:
src/├── config/│ ├── site.config.schema.ts # Zod schemas + TypeScript interfaces│ ├── site.config.default.ts # Documented template with all options│ └── site.config.ts # Actual site configuration├── lib/│ ├── config-loader.ts # Load and validate config│ ├── color-utils.ts # Color generation utilities│ └── asset-resolver.ts # Asset path resolution└── styles/ └── themes/ ├── base.css # Base theme styles ├── light.css # Light theme ├── dark.css # Dark theme └── colors.css # Generated (gitignored)Deliverables:
- ✅ Type-safe config system
- ✅ Zod validation at build time
- ✅ Comprehensive default template
- ✅ Config loader utilities
Phase 2: Color System Generation (Week 1-2)
Section titled “Phase 2: Color System Generation (Week 1-2)”Tasks:
- Create
scripts/generate-colors.mjs- Build script - Implement Tailwind scale generation (50-900) from base color
- Implement dark theme contrast adjustment algorithm
- Generate semantic color mappings (1/2/3-color aware)
- Output to
src/styles/themes/colors.css - Add color generation to
package.jsonscripts
Color Generation Algorithm:
import { readFileSync, writeFileSync } from 'fs';import { resolve } from 'path';
// Load site configconst config = await import('../src/config/site.config.ts');
// Generate Tailwind scale from base colorfunction generateTailwindScale(base) { const lightnessMap = { 50: 95, 100: 90, 200: 80, 300: 70, 400: 60, 500: base.l, // Base lightness 600: 40, 700: 30, 800: 20, 900: 10, };
return Object.entries(lightnessMap).map(([shade, lightness]) => { return { shade, hsl: `${base.h} ${base.s}% ${lightness}%`, }; });}
// Generate colors.cssfunction generateColorsCSS(config) { let css = `/* Auto-generated from site.config.ts - DO NOT EDIT */\n`; css += `/* Generated: ${new Date().toISOString()} */\n\n`;
css += `:root {\n`;
// Generate primary scale const primaryScale = generateTailwindScale(config.colors.primary.base); primaryScale.forEach(({ shade, hsl }) => { css += ` --primary-${shade}: ${hsl};\n`; });
// Generate secondary scale (if exists) if (config.colors.secondary) { const secondaryScale = generateTailwindScale(config.colors.secondary.base); secondaryScale.forEach(({ shade, hsl }) => { css += ` --secondary-${shade}: ${hsl};\n`; }); }
// Semantic mappings css += `\n /* Semantic colors */\n`; css += ` --color-brand: var(--primary-500);\n`; css += ` --color-accent: var(--secondary-500, var(--primary-600));\n`; // ... etc
css += `}\n\n`;
// Dark theme adjustments css += `[data-theme="dark"] {\n`; const adjustment = config.colors.themes.dark.contrastAdjustment || 15; const darkPrimary = adjustLightness(config.colors.primary.base, adjustment); css += ` --primary-500: ${darkPrimary.h} ${darkPrimary.s}% ${darkPrimary.l}%;\n`; // ... etc css += `}\n`;
return css;}
// Write to fileconst css = generateColorsCSS(config.default);writeFileSync('src/styles/themes/colors.css', css);console.log('✅ Generated src/styles/themes/colors.css');Package.json Scripts:
{ "scripts": { "dev": "astro dev", "build": "pnpm generate-colors && astro build", "build:production": "pnpm validate-config && pnpm generate-colors && pnpm validate-assets && astro build", "generate-colors": "node scripts/generate-colors.mjs", "validate-config": "node scripts/validate-config.mjs", "validate-assets": "node scripts/validate-assets.mjs" }}Deliverables:
- ✅ Automated color generation script
- ✅ Tailwind-compatible color scale output
- ✅ Dark theme contrast adjustments
- ✅ Integrated into build process
Phase 3: Asset Resolution System (Week 2)
Section titled “Phase 3: Asset Resolution System (Week 2)”Tasks:
- Create
src/lib/asset-resolver.ts - Implement shared vs site-specific resolution logic
- Add helper functions for logo, favicon, OG image paths
- Support monorepo shared asset paths
- Create asset validation script for production builds
Asset Resolver:
import { existsSync } from 'fs';import { resolve } from 'path';import siteConfig from '@/config/site.config';
export function resolveAsset(assetType: 'logo' | 'favicon' | 'ogImage', variant?: 'light' | 'dark') { const config = siteConfig.assets;
// Check site-specific first let assetPath; if (assetType === 'logo' && variant) { assetPath = config.logo[variant]; } else if (assetType === 'favicon') { assetPath = config.favicon?.svg; } else if (assetType === 'ogImage') { assetPath = config.ogImage; }
// If site-specific not found, try shared if (!assetPath || !existsSync(resolve(assetPath))) { if (config.shared && assetType === 'logo' && variant) { assetPath = config.shared.logo?.[variant]; } else if (config.shared && assetType === 'favicon') { assetPath = config.shared.favicon; } }
// Validate asset exists (production builds fail if not) if (import.meta.env.PROD && (!assetPath || !existsSync(resolve(assetPath)))) { throw new Error(`Missing ${assetType} asset (variant: ${variant})`); }
return assetPath;}
export function getLogoPath(theme: 'light' | 'dark' = 'light') { return resolveAsset('logo', theme);}
export function getFaviconPath() { return resolveAsset('favicon');}
export function getOgImagePath() { return resolveAsset('ogImage');}Asset Validation Script:
import { existsSync } from 'fs';import { resolve } from 'path';
const config = await import('../src/config/site.config.ts');
const requiredAssets = [ config.default.assets.logo.light, config.default.assets.logo.dark,];
if (config.default.assets.favicon?.svg) { requiredAssets.push(config.default.assets.favicon.svg);}
if (config.default.assets.ogImage) { requiredAssets.push(config.default.assets.ogImage);}
let missingAssets = [];requiredAssets.forEach(assetPath => { if (!existsSync(resolve(assetPath))) { missingAssets.push(assetPath); }});
if (missingAssets.length > 0) { console.error('❌ Missing required assets:'); missingAssets.forEach(asset => console.error(` - ${asset}`)); process.exit(1);}
console.log('✅ All required assets found');Deliverables:
- ✅ Asset resolution with fallback logic
- ✅ Build-time asset validation
- ✅ Production builds fail on missing assets
- ✅ Helper functions for common assets
Phase 4: Integration with Layouts & Components (Week 2-3)
Section titled “Phase 4: Integration with Layouts & Components (Week 2-3)”Tasks:
- Update
BaseLayout.astroto use config - Update
Header.astrocomponent - Update
Footer.astrocomponent - Update
Logo.astrocomponent - Update SEO component/layout
- Test with both TS.com and SDC.com configs
BaseLayout Integration:
---import siteConfig from '@/config/site.config';import { getLogoPath, getFaviconPath, getOgImagePath } from '@/lib/asset-resolver';
interface Props { title?: string; description?: string; ogImage?: string;}
const { title = siteConfig.seo.defaultTitle, description = siteConfig.seo.defaultDescription, ogImage = getOgImagePath(),} = Astro.props;
const pageTitle = title.includes('|') ? title : siteConfig.seo.titleTemplate.replace('{title}', title).replace('{siteName}', siteConfig.site.name);
const faviconPath = getFaviconPath();---
<!DOCTYPE html><html lang={siteConfig.site.language}><head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{pageTitle}</title> <meta name="description" content={description} />
<!-- Favicon --> <link rel="icon" type="image/svg+xml" href={faviconPath} />
<!-- Open Graph --> <meta property="og:title" content={pageTitle} /> <meta property="og:description" content={description} /> <meta property="og:image" content={ogImage} /> <meta property="og:url" content={`${siteConfig.site.url}${Astro.url.pathname}`} />
<!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image" /> {siteConfig.seo.twitterHandle && ( <meta name="twitter:site" content={siteConfig.seo.twitterHandle} /> )}
<!-- Fonts --> {siteConfig.typography.fontSource.type === 'google' && ( <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href={siteConfig.typography.fontSource.url} rel="stylesheet" /> )}
<!-- Styles --> <link rel="stylesheet" href="/src/styles/global.css" /></head><body> <slot /></body></html>Deliverables:
- ✅ All layouts use config
- ✅ Components consume config via props
- ✅ SEO metadata automated
- ✅ Tested across both sites
Phase 5: Offerings System (SDC.com Only) (Week 3-4)
Section titled “Phase 5: Offerings System (SDC.com Only) (Week 3-4)”Tasks:
- Set up Astro Content Collections for products, services, insights
- Create content schemas with country/language support
- Build dynamic page generation for
[slug].astropages - Implement country/language detection logic
- Create content filtering utilities
- Build product/service/insight page templates
Content Collection Schema:
import { defineCollection, z } from 'astro:content';
const productsCollection = defineCollection({ type: 'content', schema: z.object({ title: z.string(), description: z.string(), publishDate: z.date(), updatedDate: z.date().optional(), country: z.enum(['CA', 'US']), language: z.enum(['en', 'fr']), category: z.array(z.string()), tags: z.array(z.string()), featured: z.boolean().default(false), pricing: z.object({ amount: z.number(), currency: z.enum(['CAD', 'USD']), stripePriceId: z.string(), }), seo: z.object({ metaTitle: z.string(), metaDescription: z.string(), ogImage: z.string(), keywords: z.array(z.string()).optional(), }), }),});
const insightsCollection = defineCollection({ type: 'content', schema: z.object({ title: z.string(), description: z.string(), publishDate: z.date(), updatedDate: z.date().optional(), author: z.string(), country: z.enum(['CA', 'US']), language: z.enum(['en', 'fr']), category: z.string(), tags: z.array(z.string()), featured: z.boolean().default(false), readingTime: z.number(), excerpt: z.string(), seo: z.object({ metaTitle: z.string(), metaDescription: z.string(), ogImage: z.string(), keywords: z.array(z.string()).optional(), }), }),});
export const collections = { products: productsCollection, insights: insightsCollection,};Dynamic Page Example:
---import { getCollection, getEntry } from 'astro:content';import BaseLayout from '@/layouts/BaseLayout.astro';import ProductLayout from '@/layouts/ProductLayout.astro';import siteConfig from '@/config/site.config';
export async function getStaticPaths() { const products = await getCollection('products', ({ data }) => { // Filter by configured countries and languages return ( siteConfig.offerings.products.countries.includes(data.country) && siteConfig.offerings.products.languages.includes(data.language) ); });
return products.map(product => ({ params: { slug: product.slug }, props: { product }, }));}
const { product } = Astro.props;const { Content } = await product.render();
// Detect user's preferred country/languageconst userCountry = Astro.cookies.get('preferred-country')?.value || siteConfig.offerings.products.defaultCountry;const userLanguage = Astro.cookies.get('preferred-language')?.value || siteConfig.offerings.products.defaultLanguage;---
<BaseLayout title={product.data.seo.metaTitle} description={product.data.seo.metaDescription} ogImage={product.data.seo.ogImage}> <ProductLayout product={product.data}> <Content /> </ProductLayout></BaseLayout>Deliverables:
- ✅ Content collections configured
- ✅ Country/language support implemented
- ✅ Dynamic page generation working
- ✅ Content filtering by locale
- ✅ User preference detection
File Structure
Section titled “File Structure”monorepo/├── shared/│ └── brand/ # Shared SmartDebt assets│ ├── smart-debt-logo-light.svg│ ├── smart-debt-logo-dark.svg│ ├── smart-debt-favicon.svg│ └── smart-debt-entity.js # Web Component│├── sites/│ ├── Template/│ │ └── src/│ │ ├── config/│ │ │ ├── site.config.schema.ts # Zod schemas + TypeScript types│ │ │ ├── site.config.default.ts # Documented template│ │ │ └── site.config.ts # Actual configuration│ │ ├── lib/│ │ │ ├── config-loader.ts # Load & validate config│ │ │ ├── color-utils.ts # Color generation utilities│ │ │ └── asset-resolver.ts # Asset path resolution│ │ ├── styles/│ │ │ ├── global.css # Main stylesheet│ │ │ └── themes/│ │ │ ├── base.css # Base theme│ │ │ ├── light.css # Light theme│ │ │ ├── dark.css # Dark theme│ │ │ └── colors.css # Generated (gitignored)│ │ ├── assets/│ │ │ └── brand/ # Site-specific assets│ │ │ ├── logo-light.svg│ │ │ └── logo-dark.svg│ │ └── scripts/│ │ ├── generate-colors.mjs # Color generation│ │ ├── validate-config.mjs # Config validation│ │ └── validate-assets.mjs # Asset validation│ ││ ├── TalbotStevens/│ │ └── src/│ │ ├── config/│ │ │ └── site.config.ts # TS.com configuration│ │ ├── pages/│ │ │ ├── index.astro # Home│ │ │ ├── expertise.astro│ │ │ ├── books.astro # Links to SDC.com│ │ │ ├── speaking.astro│ │ │ ├── media.astro│ │ │ ├── contact.astro│ │ │ └── legal/│ │ │ ├── privacy.astro│ │ │ └── terms.astro│ │ ├── components/│ │ │ ├── SmartDebtCTA.astro # CTA to SDC.com│ │ │ └── LegacyWork.astro # Template for old books│ │ ├── assets/│ │ │ └── brand/│ │ │ ├── ts-logo-light.svg│ │ │ ├── ts-logo-dark.svg│ │ │ └── ts-favicon.svg│ │ └── styles/│ │ └── themes/│ │ └── colors.css # Generated│ ││ └── SmartDebtCoach/│ └── src/│ ├── config/│ │ └── site.config.ts # SDC.com configuration│ ├── pages/│ │ ├── index.astro # Home│ │ ├── products/│ │ │ ├── index.astro # Product listing│ │ │ └── [slug].astro # Dynamic product pages│ │ ├── services/│ │ │ ├── index.astro # Service listing│ │ │ └── [slug].astro # Dynamic service pages│ │ ├── insights/│ │ │ ├── index.astro # Blog listing│ │ │ └── [slug].astro # Dynamic blog posts│ │ ├── about.astro│ │ ├── contact.astro│ │ ├── checkout/│ │ │ ├── success.astro│ │ │ └── cancel.astro│ │ └── legal/│ │ ├── privacy.astro│ │ ├── terms.astro│ │ └── refunds.astro│ ├── content/│ │ ├── config.ts # Content collections schema│ │ ├── products/│ │ │ ├── debt-mastery-course/│ │ │ │ ├── en-CA.md│ │ │ │ ├── en-US.md│ │ │ │ └── meta.json│ │ │ └── ...│ │ ├── services/│ │ │ ├── group-coaching/│ │ │ │ ├── en-CA.md│ │ │ │ ├── en-US.md│ │ │ │ └── meta.json│ │ │ └── ...│ │ └── insights/│ │ ├── understanding-good-debt/│ │ │ ├── en-CA.md│ │ │ ├── en-US.md│ │ │ └── meta.json│ │ └── ...│ ├── components/│ │ ├── offerings/│ │ │ ├── ProductCard.astro│ │ │ ├── ServiceCard.astro│ │ │ └── InsightCard.astro│ │ └── commerce/│ │ ├── PricingDisplay.astro│ │ ├── CountrySelector.astro│ │ └── CheckoutButton.astro│ ├── lib/│ │ ├── offerings-utils.ts # Content filtering by country/lang│ │ ├── locale-detection.ts # Country/language detection│ │ └── stripe-utils.ts # Stripe integration helpers│ ├── assets/│ │ └── brand/│ │ └── sdc-og-image.jpg│ └── styles/│ └── themes/│ └── colors.css # Generated│└── scripts/ └── (Build scripts shared across sites)Build Process
Section titled “Build Process”Development Build
Section titled “Development Build”pnpm devProcess:
- Loads
site.config.ts - Uses existing
colors.css(or generates if missing) - Starts Astro dev server
- Hot reload on config changes (manual restart needed)
Production Build
Section titled “Production Build”pnpm build:productionProcess:
-
Validate Config (
validate-config.mjs)- Load
site.config.ts - Validate against Zod schema
- Check required fields present
- Validate color HSL values in range
- Exit with error if validation fails
- Load
-
Generate Colors (
generate-colors.mjs)- Read color configuration
- Generate Tailwind scale (50-900) from base colors
- Apply dark theme contrast adjustments
- Generate semantic color mappings
- Write to
src/styles/themes/colors.css - Log generation success
-
Validate Assets (
validate-assets.mjs)- Check all asset paths in config
- Verify files exist at specified paths
- Check shared assets (if referenced)
- Exit with error if any missing
- Log validation success
-
Astro Build
- Build site with validated config
- Optimize images
- Generate static pages
- Bundle assets
Example Output:
$ pnpm build:production
> validate-config✅ Configuration valid
> generate-colors✅ Generated src/styles/themes/colors.css (1.2 KB)
> validate-assets✅ All required assets found: - src/assets/brand/ts-logo-light.svg - src/assets/brand/ts-logo-dark.svg - public/brand/ts-favicon.svg - public/brand/ts-og-image.jpg
> astro buildBuilding for production...✓ Built in 4.2sError Handling Example:
$ pnpm build:production
> validate-config❌ Configuration validation failed: - colors.primary.base.h must be between 0 and 360 (got: 400) - seo.defaultTitle is required
> Process exited with code 1Validation Rules
Section titled “Validation Rules”Config Validation (Zod Schema)
Section titled “Config Validation (Zod Schema)”import { z } from 'zod';
const HSLColorSchema = z.object({ h: z.number().min(0).max(360), s: z.number().min(0).max(100), l: z.number().min(0).max(100),});
const SiteConfigSchema = z.object({ site: z.object({ name: z.string().min(1), brandName: z.string().min(1), entity: z.enum(['Coach', 'App', 'Wealth']).optional(), tagline: z.string().optional(), url: z.string().url(), language: z.string().length(2).default('en'), country: z.enum(['CA', 'US']), }),
assets: z.object({ shared: z.object({ logo: z.object({ light: z.string(), dark: z.string(), }).optional(), favicon: z.string().optional(), smartDebtEntity: z.boolean().optional(), }).optional(), logo: z.object({ light: z.string(), dark: z.string(), }), favicon: z.object({ svg: z.string().optional(), generateFromLogo: z.boolean().optional(), }).optional(), ogImage: z.string().optional(), }),
colors: z.object({ primary: z.object({ base: HSLColorSchema, generateScale: z.boolean().default(true), overrides: z.record(HSLColorSchema.or(z.string())).optional(), }), secondary: z.object({ base: HSLColorSchema, generateScale: z.boolean().default(true), overrides: z.record(HSLColorSchema.or(z.string())).optional(), }).optional(), tertiary: z.object({ base: HSLColorSchema, generateScale: z.boolean().default(true), overrides: z.record(HSLColorSchema.or(z.string())).optional(), }).optional(), logo: z.object({ light: z.object({ bg: HSLColorSchema.or(z.string()), text: HSLColorSchema.or(z.string()), }), dark: z.object({ bg: HSLColorSchema.or(z.string()), text: HSLColorSchema.or(z.string()), }), }), themes: z.object({ light: z.object({ primary: HSLColorSchema.optional(), background: HSLColorSchema.optional(), foreground: HSLColorSchema.optional(), }).optional(), dark: z.object({ autoAdjustContrast: z.boolean().default(true), contrastAdjustment: z.number().min(0).max(50).default(15), primary: HSLColorSchema.optional(), background: HSLColorSchema.optional(), foreground: HSLColorSchema.optional(), }), }), }),
typography: z.object({ fontFamily: z.object({ primary: z.string(), fallback: z.string().default('system-ui, -apple-system, sans-serif'), }), fontSource: z.object({ type: z.enum(['google', 'local', 'none']), url: z.string().optional(), weights: z.array(z.number()).default([400, 500, 600, 700, 900]), }), fontWeights: z.object({ body: z.number().default(400), medium: z.number().default(500), semibold: z.number().default(600), bold: z.number().default(700), heavy: z.number().default(900), }), }),
seo: z.object({ defaultTitle: z.string().min(1), titleTemplate: z.string().optional(), defaultDescription: z.string().min(50).max(160), ogImage: z.string(), twitterHandle: z.string().optional(), copyright: z.string(), allowPageOverrides: z.boolean().default(true), }),
navigation: z.object({ header: z.object({ layout: z.enum(['default', 'centered', 'split']).default('default'), sticky: z.boolean().default(false), showLogo: z.boolean(), showBrandName: z.boolean(), entity: z.string().optional(), menu: z.array(z.object({ label: z.string(), url: z.string(), external: z.boolean().optional(), children: z.array(z.object({ label: z.string(), url: z.string(), external: z.boolean().optional(), })).optional(), })), }), footer: z.object({ description: z.string(), links: z.object({ primary: z.array(z.object({ label: z.string(), url: z.string(), })), legal: z.array(z.object({ label: z.string(), url: z.string(), })), social: z.array(z.object({ label: z.string(), url: z.string(), icon: z.string().optional(), })).optional(), }), }), }),
theme: z.object({ defaultTheme: z.enum(['light', 'dark', 'system']).default('system'), allowToggle: z.boolean().default(true), storageKeys: z.object({ theme: z.string().default('theme-preference'), }), }),
offerings: z.object({ products: z.object({ enabled: z.boolean(), basePath: z.string(), dataPath: z.string(), countries: z.array(z.enum(['CA', 'US'])), languages: z.array(z.enum(['en', 'fr'])), defaultCountry: z.enum(['CA', 'US']), defaultLanguage: z.enum(['en', 'fr']), }).optional(), services: z.object({ enabled: z.boolean(), basePath: z.string(), dataPath: z.string(), countries: z.array(z.enum(['CA', 'US'])), languages: z.array(z.enum(['en', 'fr'])), defaultCountry: z.enum(['CA', 'US']), defaultLanguage: z.enum(['en', 'fr']), }).optional(), content: z.object({ enabled: z.boolean(), basePath: z.string(), dataPath: z.string(), countries: z.array(z.enum(['CA', 'US'])), languages: z.array(z.enum(['en', 'fr'])), defaultCountry: z.enum(['CA', 'US']), defaultLanguage: z.enum(['en', 'fr']), categories: z.array(z.string()), relatedArticles: z.boolean().default(true), }).optional(), }).optional(),
commerce: z.object({ provider: z.literal('stripe'), stripe: z.object({ publicKey: z.string(), accounts: z.record(z.object({ publicKey: z.string(), })).optional(), }), defaultCurrency: z.enum(['CAD', 'USD']), currencyByCountry: z.record(z.enum(['CAD', 'USD'])), taxIncluded: z.boolean(), checkout: z.object({ successUrl: z.string(), cancelUrl: z.string(), }), }).optional(),
analytics: z.object({ plausible: z.object({ domain: z.string(), customDomain: z.string().optional(), }).optional(), gtm: z.object({ id: z.string(), }).optional(), }).optional(),
integrations: z.object({ forms: z.object({ provider: z.enum(['web3forms', 'formspree']), apiKey: z.string(), honeypot: z.boolean().default(true), features: z.object({ captcha: z.enum(['recaptcha', 'hcaptcha', 'turnstile']).optional(), webhooks: z.array(z.string()).optional(), }).optional(), }).optional(), auth: z.object({ provider: z.enum(['supabase', 'espocrm']), config: z.record(z.any()), }).optional(), }).optional(),
build: z.object({ colors: z.object({ regenerate: z.enum(['on-change', 'always']).default('on-change'), outputPath: z.string().default('src/styles/themes/colors.css'), }), assets: z.object({ validateOnBuild: z.boolean().default(true), failOnMissing: z.boolean().default(true), }), images: z.object({ formats: z.array(z.enum(['webp', 'avif'])).default(['webp', 'avif']), quality: z.number().min(1).max(100).default(80), }), }),});
export type SiteConfig = z.infer<typeof SiteConfigSchema>;
export function defineConfig(config: SiteConfig) { return SiteConfigSchema.parse(config);}Asset Validation Rules
Section titled “Asset Validation Rules”Required Assets:
assets.logo.light- Must existassets.logo.dark- Must existassets.favicon.svg(orassets.favicon.generateFromLogo= true)seo.ogImage- Must exist
Shared Assets (if referenced):
assets.shared.logo.light- Must exist if no site-specific logoassets.shared.logo.dark- Must exist if no site-specific logoassets.shared.favicon- Must exist if no site-specific favicon
Validation Process:
- Check site-specific assets first
- If missing, check shared assets (if configured)
- If still missing, error and halt build
- Log all validated assets for confirmation
Migration Path
Section titled “Migration Path”Migrating Existing Sites to Config System
Section titled “Migrating Existing Sites to Config System”Step 1: Create Initial Config
# Copy default templatecp src/config/site.config.default.ts src/config/site.config.ts
# Fill in site-specific values# Edit site.config.tsStep 2: Extract Current Colors
# Analyze existing CSS for color values# Convert to HSL format# Add to colors section of configStep 3: Update Layouts/Components
# Replace hardcoded values with config references# Update imports to use config-loader# Test page renderingStep 4: Generate Colors
# Run color generationpnpm generate-colors
# Verify output in colors.css# Compare with existing styles# Adjust if neededStep 5: Validate Build
# Run production buildpnpm build:production
# Fix any validation errors# Verify all assets found# Test generated siteDocumentation
Section titled “Documentation”For Developers
Section titled “For Developers”Config Reference:
- Complete property documentation in
site.config.default.ts - JSDoc comments on all properties
- Examples for common scenarios
- Validation error explanations
Usage Guides:
- Creating a new site from Template
- Customizing colors and themes
- Adding new offerings (SDC.com)
- Multi-country/language setup
- Asset management strategies
API Reference:
- Config loader utilities
- Asset resolver helpers
- Color generation functions
- Offering filtering utilities
For Content Creators
Section titled “For Content Creators”Content Structure:
- How to create products/services/articles
- Country/language variant requirements
- Frontmatter schema documentation
- Asset organization guidelines
SEO Guidelines:
- Meta title/description best practices
- OG image specifications
- Keyword targeting strategies
- Country-specific SEO considerations
Testing Strategy
Section titled “Testing Strategy”Unit Tests
Section titled “Unit Tests”Config Validation:
import { describe, it, expect } from 'vitest';import { SiteConfigSchema } from '@/config/site.config.schema';
describe('Site Config Validation', () => { it('should validate valid config', () => { const validConfig = { site: { name: "Test Site", brandName: "Test Brand", url: "https://example.com", language: "en", country: "CA", }, // ... minimal valid config };
expect(() => SiteConfigSchema.parse(validConfig)).not.toThrow(); });
it('should reject invalid HSL values', () => { const invalidConfig = { // ... config with h: 400 (out of range) };
expect(() => SiteConfigSchema.parse(invalidConfig)).toThrow(); });
// More tests...});Color Generation:
import { describe, it, expect } from 'vitest';import { generateTailwindScale, adjustLightness } from '@/lib/color-utils';
describe('Color Generation', () => { it('should generate correct Tailwind scale', () => { const base = { h: 217, s: 71, l: 40 }; const scale = generateTailwindScale(base);
expect(scale[50].l).toBe(95); expect(scale[500].l).toBe(40); expect(scale[900].l).toBe(10); });
it('should adjust lightness correctly', () => { const base = { h: 217, s: 71, l: 40 }; const adjusted = adjustLightness(base, 15);
expect(adjusted.l).toBe(55); });
// More tests...});Integration Tests
Section titled “Integration Tests”Build Process:
import { describe, it, expect } from 'vitest';import { exec } from 'child_process';import { promisify } from 'util';
const execAsync = promisify(exec);
describe('Build Process', () => { it('should fail if config is invalid', async () => { // Create invalid config // Run build // Expect error });
it('should generate colors.css', async () => { await execAsync('pnpm generate-colors');
// Verify colors.css exists // Verify contains expected variables });
it('should validate assets before build', async () => { // Remove required asset // Run build // Expect error with missing asset message });});E2E Tests
Section titled “E2E Tests”Page Rendering:
import { test, expect } from '@playwright/test';
test('homepage renders with correct config values', async ({ page }) => { await page.goto('/');
// Check title from config await expect(page).toHaveTitle(/Test Site/);
// Check logo loads await expect(page.locator('img[alt="Test Site Logo"]')).toBeVisible();
// Check theme colors applied const primaryColor = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--primary-500') ); expect(primaryColor).toBeTruthy();});Future Enhancements
Section titled “Future Enhancements”Phase 2 Features (Post-Launch)
Section titled “Phase 2 Features (Post-Launch)”-
Visual Config Editor
- Web-based UI for editing
site.config.ts - Live preview of color changes
- Asset upload/management
- Export/import configurations
- Web-based UI for editing
-
Multi-language Full Support
- French translations for SMART DEBT Insights
- Translation management workflow
- Automatic translation status tracking
- Translation quality checks
-
Advanced Offerings Features
- Product recommendations engine
- Cross-sell/upsell suggestions
- Dynamic pricing (sales, promotions)
- Inventory management (digital products)
-
Enhanced Analytics
- Conversion funnel tracking
- A/B test result dashboards
- Heatmap integration
- User journey visualization
-
Content Personalization
- Geo-targeted content
- User preference-based recommendations
- Dynamic hero/CTA variations
- Returning visitor detection
Potential Config Additions
Section titled “Potential Config Additions”// Future config sections
// Marketing automationmarketing?: { automation: { provider: 'activecampaign' | 'convertkit'; apiKey: string; lists: { newsletter: string; leads: string; customers: string; }; };};
// Advanced SEOseo: { // ... existing schema: { organization: { name: string; logo: string; socialProfiles: string[]; }; person: { name: string; jobTitle: string; image: string; }; }; hreflang: { enabled: boolean; alternates: Array<{ language: string; country: string; url: string; }>; };};
// Performance optimizationperformance?: { lazyLoading: { images: boolean; components: string[]; // Component names to lazy load }; prefetch: { enabled: boolean; routes: string[]; // Routes to prefetch }; caching: { strategy: 'networkFirst' | 'cacheFirst' | 'staleWhileRevalidate'; maxAge: number; // Seconds };};Appendix
Section titled “Appendix”A. Color Scale Reference
Section titled “A. Color Scale Reference”Tailwind Scale Mapping:
50 - Lightest (95% lightness) - Backgrounds, subtle accents100 - Very Light (90%) - Hover states on light backgrounds200 - Light (80%) - Borders, dividers300 - Soft (70%) - Disabled states, placeholders400 - Medium Light (60%) - Secondary text500 - Base (from config) - Primary buttons, links, brand elements600 - Medium Dark (40%) - Hover states on primary buttons700 - Dark (30%) - Active states, pressed buttons800 - Very Dark (20%) - High contrast text900 - Darkest (10%) - Maximum contrast, dark mode backgroundsB. HSL Color Tips
Section titled “B. HSL Color Tips”Hue (H: 0-360):
- 0° / 360° = Red
- 30° = Orange
- 60° = Yellow
- 120° = Green
- 180° = Cyan
- 240° = Blue
- 300° = Magenta
Saturation (S: 0-100%):
- 0% = Grayscale (no color)
- 50% = Moderate saturation
- 100% = Fully saturated (vibrant)
Lightness (L: 0-100%):
- 0% = Black
- 50% = Pure color
- 100% = White
Recommended Brand Color Ranges:
- H: 200-240° (Blues) - Trust, professionalism
- H: 30-60° (Oranges/Yellows) - Energy, optimism
- H: 120-150° (Greens) - Growth, wealth
- S: 60-80% - Vibrant but not overwhelming
- L: 40-50% - Good contrast for text/backgrounds
C. Asset Specifications
Section titled “C. Asset Specifications”Logo Files:
- Format: SVG (vector, scalable)
- Size: Optimized, < 50 KB
- Colors: Match theme colors or standalone
- Variants: Light theme and dark theme versions
- Dimensions: Flexible, but test at 32px, 64px, 128px heights
Favicon:
- Format: SVG (modern browsers) + ICO fallback
- SVG: Single color or simple design, < 5 KB
- ICO: 16x16, 32x32, 48x48 resolutions
OG Images:
- Format: JPG or PNG
- Dimensions: 1200x630px (Facebook/LinkedIn standard)
- Twitter: 1200x600px (optional separate image)
- File Size: < 1 MB
- Content: Include logo, headline, brand colors
D. Country/Language Codes
Section titled “D. Country/Language Codes”Supported Countries:
CA- CanadaUS- United States
Supported Languages:
en- Englishfr- French (future)
Locale Combinations:
en-CA- English (Canada)en-US- English (United States)fr-CA- French (Canada) - future
E. Environment Variables
Section titled “E. Environment Variables”Required for Production:
# Stripe (SDC.com only)PUBLIC_STRIPE_KEY=pk_live_...STRIPE_SECRET_KEY=sk_live_...
# FormsPUBLIC_WEB3FORMS_KEY=...
# Webhooks (n8n)N8N_WEBHOOK_URL=https://...
# Analytics (optional)PUBLIC_PLAUSIBLE_DOMAIN=smartdebtcoach.comDevelopment:
# Use test keysPUBLIC_STRIPE_KEY=pk_test_...STRIPE_SECRET_KEY=sk_test_...F. Glossary
Section titled “F. Glossary”- SSoT - Single Source of Truth
- HSL - Hue, Saturation, Lightness (color model)
- OG - Open Graph (meta tags for social media)
- CTA - Call to Action
- SDC - SmartDebtCoach.com
- TS - TalbotStevens.com
- Locale - Country + Language combination (e.g., en-CA)
- Variant - Country/language specific version of content
Changelog
Section titled “Changelog”| Version | Date | Changes |
|---|---|---|
| 2.0 | 2025-11-13 | Complete rewrite based on clarifications: - Tailwind color scale (50-900) - Production-build validation - TS.com as gateway site - SDC.com as content hub - Offerings configuration - Country/language support |
| 1.0 | 2025-11-03 | Initial plan |
END OF SITE CONFIG PLAN V2