Skip to content

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

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

  • 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

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
}

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/ or public/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",
}

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,
},
},
}

Build-Time Generation via: pnpm build:production

Process:

  1. Script reads site.config.ts
  2. Generates Tailwind color scale (50-900) from base colors using HSL lightness progression
  3. Applies dark theme contrast adjustments
  4. Creates semantic color mappings based on color count (1/2/3-color aware)
  5. Outputs to src/styles/themes/colors.css
  6. Validates all color values and contrast ratios

Tailwind Scale Generation Algorithm:

// From base color at 500, generate full scale
const 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

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,
},
}

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):

src/pages/about.astro
---
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 config
seo: {
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" },
],
},
},
}

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.


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:

src/content/insights/understanding-good-debt/en-CA.md
---
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-15
updatedDate: 2025-02-10
author: "Talbot Stevens"
category: "Debt Strategy"
tags: ["good debt", "leverage", "investing"]
country: "CA"
language: "en"
featured: true
seo:
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"
};
}

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>;
};
};

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
};
}

sites/TalbotStevens/src/config/site.config.ts
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,
},
},
});

sites/SmartDebtCoach/src/config/site.config.ts
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,
},
},
});

Phase 1: Configuration Infrastructure (Week 1)

Section titled “Phase 1: Configuration Infrastructure (Week 1)”

Tasks:

  1. Create TypeScript schema with Zod validation
  2. Create site.config.schema.ts with all interfaces
  3. Create site.config.default.ts with documented template
  4. Create src/lib/config-loader.ts - Load and validate config
  5. 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:

  1. Create scripts/generate-colors.mjs - Build script
  2. Implement Tailwind scale generation (50-900) from base color
  3. Implement dark theme contrast adjustment algorithm
  4. Generate semantic color mappings (1/2/3-color aware)
  5. Output to src/styles/themes/colors.css
  6. Add color generation to package.json scripts

Color Generation Algorithm:

scripts/generate-colors.mjs
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
// Load site config
const config = await import('../src/config/site.config.ts');
// Generate Tailwind scale from base color
function 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.css
function 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 file
const 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

Tasks:

  1. Create src/lib/asset-resolver.ts
  2. Implement shared vs site-specific resolution logic
  3. Add helper functions for logo, favicon, OG image paths
  4. Support monorepo shared asset paths
  5. Create asset validation script for production builds

Asset Resolver:

src/lib/asset-resolver.ts
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:

scripts/validate-assets.mjs
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:

  1. Update BaseLayout.astro to use config
  2. Update Header.astro component
  3. Update Footer.astro component
  4. Update Logo.astro component
  5. Update SEO component/layout
  6. Test with both TS.com and SDC.com configs

BaseLayout Integration:

src/layouts/BaseLayout.astro
---
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:

  1. Set up Astro Content Collections for products, services, insights
  2. Create content schemas with country/language support
  3. Build dynamic page generation for [slug].astro pages
  4. Implement country/language detection logic
  5. Create content filtering utilities
  6. Build product/service/insight page templates

Content Collection Schema:

src/content/config.ts
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:

src/pages/products/[slug].astro
---
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/language
const 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

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)

Terminal window
pnpm dev

Process:

  1. Loads site.config.ts
  2. Uses existing colors.css (or generates if missing)
  3. Starts Astro dev server
  4. Hot reload on config changes (manual restart needed)
Terminal window
pnpm build:production

Process:

  1. 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
  2. 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
  3. 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
  4. 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 build
Building for production...
✓ Built in 4.2s

Error 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 1

src/config/site.config.schema.ts
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);
}

Required Assets:

  • assets.logo.light - Must exist
  • assets.logo.dark - Must exist
  • assets.favicon.svg (or assets.favicon.generateFromLogo = true)
  • seo.ogImage - Must exist

Shared Assets (if referenced):

  • assets.shared.logo.light - Must exist if no site-specific logo
  • assets.shared.logo.dark - Must exist if no site-specific logo
  • assets.shared.favicon - Must exist if no site-specific favicon

Validation Process:

  1. Check site-specific assets first
  2. If missing, check shared assets (if configured)
  3. If still missing, error and halt build
  4. Log all validated assets for confirmation

Step 1: Create Initial Config

Terminal window
# Copy default template
cp src/config/site.config.default.ts src/config/site.config.ts
# Fill in site-specific values
# Edit site.config.ts

Step 2: Extract Current Colors

Terminal window
# Analyze existing CSS for color values
# Convert to HSL format
# Add to colors section of config

Step 3: Update Layouts/Components

Terminal window
# Replace hardcoded values with config references
# Update imports to use config-loader
# Test page rendering

Step 4: Generate Colors

Terminal window
# Run color generation
pnpm generate-colors
# Verify output in colors.css
# Compare with existing styles
# Adjust if needed

Step 5: Validate Build

Terminal window
# Run production build
pnpm build:production
# Fix any validation errors
# Verify all assets found
# Test generated site

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

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

Config Validation:

tests/config/validation.test.ts
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:

tests/lib/color-utils.test.ts
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...
});

Build Process:

tests/build/build-process.test.ts
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
});
});

Page Rendering:

tests/e2e/page-rendering.test.ts
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();
});

  1. Visual Config Editor

    • Web-based UI for editing site.config.ts
    • Live preview of color changes
    • Asset upload/management
    • Export/import configurations
  2. Multi-language Full Support

    • French translations for SMART DEBT Insights
    • Translation management workflow
    • Automatic translation status tracking
    • Translation quality checks
  3. Advanced Offerings Features

    • Product recommendations engine
    • Cross-sell/upsell suggestions
    • Dynamic pricing (sales, promotions)
    • Inventory management (digital products)
  4. Enhanced Analytics

    • Conversion funnel tracking
    • A/B test result dashboards
    • Heatmap integration
    • User journey visualization
  5. Content Personalization

    • Geo-targeted content
    • User preference-based recommendations
    • Dynamic hero/CTA variations
    • Returning visitor detection
// Future config sections
// Marketing automation
marketing?: {
automation: {
provider: 'activecampaign' | 'convertkit';
apiKey: string;
lists: {
newsletter: string;
leads: string;
customers: string;
};
};
};
// Advanced SEO
seo: {
// ... 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 optimization
performance?: {
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
};
};

Tailwind Scale Mapping:

50 - Lightest (95% lightness) - Backgrounds, subtle accents
100 - Very Light (90%) - Hover states on light backgrounds
200 - Light (80%) - Borders, dividers
300 - Soft (70%) - Disabled states, placeholders
400 - Medium Light (60%) - Secondary text
500 - Base (from config) - Primary buttons, links, brand elements
600 - Medium Dark (40%) - Hover states on primary buttons
700 - Dark (30%) - Active states, pressed buttons
800 - Very Dark (20%) - High contrast text
900 - Darkest (10%) - Maximum contrast, dark mode backgrounds

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

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

Supported Countries:

  • CA - Canada
  • US - United States

Supported Languages:

  • en - English
  • fr - French (future)

Locale Combinations:

  • en-CA - English (Canada)
  • en-US - English (United States)
  • fr-CA - French (Canada) - future

Required for Production:

# Stripe (SDC.com only)
PUBLIC_STRIPE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
# Forms
PUBLIC_WEB3FORMS_KEY=...
# Webhooks (n8n)
N8N_WEBHOOK_URL=https://...
# Analytics (optional)
PUBLIC_PLAUSIBLE_DOMAIN=smartdebtcoach.com

Development:

# Use test keys
PUBLIC_STRIPE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
  • 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

VersionDateChanges
2.02025-11-13Complete 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.02025-11-03Initial plan

END OF SITE CONFIG PLAN V2