Skip to content

Latest commit

Β 

History

History
511 lines (415 loc) Β· 18 KB

File metadata and controls

511 lines (415 loc) Β· 18 KB

MyBiz.Fit β€” Configurable Theme System Implementation Plan

Goal

Build a flexible theme system for MyBiz.Fit tenant landing pages so each SMB gets a unique look (colors, palette) while sharing the same component structure. The system must be:

  • YAML-driven β€” no web admin, just edit files
  • Preset + override β€” tenants pick a theme preset, can override specific tokens
  • Agent-friendly β€” a prompt template lets coding agents generate tenant configs
  • Light + dark β€” support both modes across 8 curated presets

Architecture

mybiz/
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ themes/                    ← 8 theme presets (NEW)
β”‚   β”‚   β”œβ”€β”€ dark-amber.yaml
β”‚   β”‚   β”œβ”€β”€ dark-ocean.yaml
β”‚   β”‚   β”œβ”€β”€ dark-forest.yaml
β”‚   β”‚   β”œβ”€β”€ dark-rose.yaml
β”‚   β”‚   β”œβ”€β”€ light-minimal.yaml
β”‚   β”‚   β”œβ”€β”€ light-ocean.yaml
β”‚   β”‚   β”œβ”€β”€ light-warm.yaml
β”‚   β”‚   └── light-lavender.yaml
β”‚   β”œβ”€β”€ templates/                 ← 8 business-type presets (NEW)
β”‚   β”‚   β”œβ”€β”€ restaurant.yaml
β”‚   β”‚   β”œβ”€β”€ salon.yaml
β”‚   β”‚   β”œβ”€β”€ contractor.yaml
β”‚   β”‚   β”œβ”€β”€ dental.yaml
β”‚   β”‚   β”œβ”€β”€ real-estate.yaml
β”‚   β”‚   β”œβ”€β”€ fitness.yaml
β”‚   β”‚   β”œβ”€β”€ retail.yaml
β”‚   β”‚   └── professional.yaml
β”‚   └── tenants/                   ← per-tenant configs (EXISTING)
β”‚       └── rgresidences.yaml
β”œβ”€β”€ prompts/
β”‚   └── tenant-config.yaml         ← coding agent prompt template (NEW)
β”œβ”€β”€ layouts/
β”‚   β”œβ”€β”€ _default/
β”‚   β”‚   β”œβ”€β”€ baseof.html            ← MODIFY: inject CSS vars from theme
β”‚   β”‚   └── list.html              ← MODIFY: load theme, resolve accent
β”‚   └── index.html                 ← NO CHANGE (homepage uses brand theme)
β”œβ”€β”€ static/css/
β”‚   └── landing.css                ← MODIFY: replace hardcoded colors
└── hugo.yaml

Step 1: Theme Presets (data/themes/)

Schema

Each theme YAML defines a complete color palette:

name: "Dark Amber"
mode: "dark"
colors:
  bg: "#06070b"
  bg2: "#0a0c13"
  panel: "rgba(255,255,255,0.03)"
  line: "rgba(255,255,255,0.09)"
  text: "#eceef5"
  muted: "#8d93a6"
  dim: "#5d6273"
  accent: "#ffb000"
  accent2: "#ffcf57"
  accentRGB: "255,176,0"

The accentRGB field is needed because CSS rgba() cannot accept hex values. Hugo has no built-in hex-to-RGB conversion, so we store the RGB triplet as a string for use in rgba(var(--accent-rgb), 0.x) patterns.

8 Presets

Preset Mode Accent Mood
dark-amber dark #ffb000 Warm, premium (current default)
dark-ocean dark #00b4d8 Tech, modern, cool
dark-forest dark #2ee6a6 Natural, fresh, eco
dark-rose dark #ff6b9d Elegant, feminine, beauty
light-minimal light #1a1a2e Clean, minimal, corporate
light-ocean light #0077b6 Bright, professional, trust
light-warm light #e07a5f Friendly, approachable, local
light-lavender light #7b2cbf Creative, artistic, boutique

Light mode color mapping

Light themes swap the dark/light values:

# Example: light-ocean
colors:
  bg: "#f8f9fa"          # was dark, now light
  bg2: "#ffffff"          # was darker, now white
  panel: "rgba(0,0,0,0.03)"  # was white alpha, now black alpha
  line: "rgba(0,0,0,0.09)"   # was white alpha, now black alpha
  text: "#1a1a2e"         # was light, now dark
  muted: "#4a5568"        # was light-muted, now dark-muted
  dim: "#a0aec0"          # was dark-dim, now light-dim
  accent: "#0077b6"
  accent2: "#00a8e8"
  accentRGB: "0,119,182"

Step 2: Business Templates (data/templates/)

Each template extends the tenant YAML schema with sensible defaults for a specific business type. Templates are NOT code β€” they're reference YAML files that get copied and customized.

Schema (extends tenant YAML)

# Template metadata
_template: true
_templateName: "Restaurant"
_templateDescription: "For restaurants, cafes, food trucks, and catering businesses"

# ── Defaults for this business type ──
theme: "dark-amber"
accent: null

business:
  name: null
  tagline: "Fresh Food, Made with Love"
  description: "Tell customers about your restaurant..."
  heroImage: null

sections:
  hero: true
  services: true        # β†’ mapped to "Menu Categories"
  video: false
  gallery: true
  about: true
  contact: true
  testimonials: true
  branches: false
  products: false
  cta: true

services:
  - name: "Dine-In"
    description: "Enjoy our cozy atmosphere"
    icon: "🍽️"
  - name: "Takeout"
    description: "Quick pickup, same great taste"
    icon: "πŸ₯‘"
  - name: "Catering"
    description: "Events and parties"
    icon: "πŸŽ‰"

cta:
  text: "Reserve Your Table"
  button: "Chat with Us"
  action: "chat"

8 Templates

Template Default Sections Unique Defaults
restaurant hero, services, gallery, about, contact, testimonials, cta Services β†’ menu categories, CTA β†’ "Reserve Your Table"
salon hero, services, gallery, about, contact, testimonials, cta Services β†’ hair/nail/spa, CTA β†’ "Book an Appointment"
contractor hero, services, about, contact, testimonials, cta Services β†’ plumbing/electrical/remodel, CTA β†’ "Get a Free Quote"
dental hero, services, about, contact, testimonials, cta Services β†’ cleanings/whitening/ortho, CTA β†’ "Schedule a Visit"
real-estate hero, services, gallery, about, contact, branches, cta Services β†’ buying/selling/renting, branches β†’ office locations
fitness hero, services, video, about, contact, testimonials, cta Services β†’ classes/training/equipment, CTA β†’ "Start Free Trial"
retail hero, products, gallery, about, contact, cta products β†’ product catalog, gallery β†’ product photos
professional hero, services, about, contact, testimonials, cta Services β†’ consulting/auditing/strategy, CTA β†’ "Book a Consultation"

Step 3: Tenant YAML Schema (updated)

Single YAML file per tenant. References a theme by name, can override specific tokens.

# ── Theme ──
theme: "dark-amber"
accent: null                     # null = use theme default, or hex to override

# ── Business Info ──
business:
  name: "string"
  tagline: "string"
  description: "string"
  heroImage: "/path/to/image.jpg"

# ── Section Toggles ──
sections:
  hero: true
  services: true
  video: false
  gallery: false
  about: true
  contact: true
  testimonials: false
  branches: false
  products: false
  cta: true

# ── Section Data (only needed if section is true) ──
services: [...]
video: {...}
gallery: [...]
about: {...}
branches: [...]
products: [...]
cta: {...}
contact: {...}
testimonials: [...]

# ── Chat Config ──
chat:
  welcome: "Hi! How can I help you today?"
  companyName: null             # null = use business.name

Backward compatibility

The existing rgresidences.yaml uses the old schema (top-level accent, no theme: key). The template update will:

  1. Add theme: "dark-amber" field
  2. Add chat: block with welcome and companyName
  3. Keep all existing data fields unchanged

Step 4: CSS Variable Injection

Problem

The current CSS hardcodes rgba(255,176,0,...) in 15 places. When a tenant picks a different accent color, these won't update.

Solution

  1. Add accentRGB to each theme preset (e.g., "255,176,0")
  2. In baseof.html: inject an inline <style> block that sets all CSS variables from the resolved theme
  3. In landing.css: replace all 15 hardcoded rgba(255,176,0,...) with rgba(var(--accent-rgb), ...)

baseof.html inline style

{{ if or (eq .Params.layout "landing") (eq .Params.layout "home") }}
<style>
  :root {
    --bg: {{ $theme.colors.bg }};
    --bg2: {{ $theme.colors.bg2 }};
    --panel: {{ $theme.colors.panel }};
    --line: {{ $theme.colors.line }};
    --text: {{ $theme.colors.text }};
    --muted: {{ $theme.colors.muted }};
    --dim: {{ $theme.colors.dim }};
    --accent: {{ $accent }};
    --accent2: {{ $accent2 }};
    --accent-rgb: {{ $accentRGB }};
  }
  body { background: var(--bg); color: var(--text); }
</style>
{{ end }}

Note: The inline <style> must appear BEFORE the <link rel="stylesheet" href="/css/landing.css"> so that the CSS file can use the variables. Actually β€” CSS custom properties are live, so the <style> can appear anywhere in <head> and the CSS file will pick them up. But placing it before the stylesheet is cleaner.

landing.css replacements

All 15 occurrences of rgba(255,176,0,...) become rgba(var(--accent-rgb), ...):

Line Current Replacement
48 rgba(255,176,0,0.08) rgba(var(--accent-rgb),0.08)
49 rgba(255,176,0,0.2) rgba(var(--accent-rgb),0.2)
115 rgba(255,176,0,0.3) rgba(var(--accent-rgb),0.3)
194 rgba(255,176,0,0.35) rgba(var(--accent-rgb),0.35)
253 rgba(255,176,0,0.1) rgba(var(--accent-rgb),0.1)
253 rgba(255,176,0,0.02) rgba(var(--accent-rgb),0.02)
254 rgba(255,176,0,0.15) rgba(var(--accent-rgb),0.15)
255 rgba(255,176,0,0.15) rgba(var(--accent-rgb),0.15)
619 rgba(255,176,0,0.06) rgba(var(--accent-rgb),0.06)
775 rgba(255,176,0,0.4) rgba(var(--accent-rgb),0.4)
776 rgba(255,176,0,0.2) rgba(var(--accent-rgb),0.2)
874 rgba(255,176,0,0.2) rgba(var(--accent-rgb),0.2)
1013 rgba(255,176,0,0.12) rgba(var(--accent-rgb),0.12)
1013 rgba(255,176,0,0.02) rgba(var(--accent-rgb),0.02)
1014 rgba(255,176,0,0.15) rgba(var(--accent-rgb),0.15)
1015 rgba(255,176,0,0.15) rgba(var(--accent-rgb),0.15)
1122 rgba(255,176,0,0.2) rgba(var(--accent-rgb),0.2)

Also remove the hardcoded :root variable defaults from landing.css (lines 1-14) since they'll be injected by the template. Keep them as fallback for development/testing only.


Step 5: Template Updates

list.html changes

At the top of the landing block (after line 3), add theme loading:

{{/* ── Theme Resolution ── */}}
{{ $themeName := $tenant.theme | default "dark-amber" }}
{{ $theme := index site.Data.themes $themeName }}
{{ $accent := $tenant.accent | default $theme.colors.accent }}
{{ $accent2 := $tenant.accent2 | default $theme.colors.accent2 }}
{{ $accentRGB := $theme.colors.accentRGB | default "255,176,0" }}

Replace the old $accent resolution (line 4):

{{/* OLD */}}
{{ $accent := $tenant.accent | default "#ff5733" }}
{{/* NEW β€” removed, replaced above */}}

Also update the chat widget config to use $tenant.chat.welcome with fallback:

{{ $chatWelcome := $tenant.chat.welcome | default "Hi! How can I help you today?" }}
{{ $chatCompany := $tenant.chat.companyName | default $tenant.business.name }}

baseof.html changes

Add the inline <style> block inside the <head> for landing/home layouts, BEFORE the CSS link. The variables $theme, $accent, $accent2, $accentRGB must be passed from the content page context β€” but baseof.html doesn't have access to tenant data.

Solution: Use Hugo's .Scratch or define blocks. The cleanest approach: define the variables in list.html using block "head" and override the head in baseof.html.

Actually, the simpler approach: inject the CSS variables in list.html right after the <body> tag, not in baseof.html. Since list.html defines the "main" block, we can add an inline style at the start of the landing layout:

{{ define "main" }}
  {{ if eq .Params.layout "landing" }}
    {{/* ... theme resolution ... */}}
    <style>:root { --accent: {{ $accent }}; ... }</style>
    {{/* ... rest of template ... */}}
  {{ end }}
{{ end }}

This is cleaner because all theme logic stays in list.html where tenant data is available.


Step 6: Prompt Template (prompts/tenant-config.yaml)

A YAML template that coding agents fill in to generate tenant configs.

# ═══════════════════════════════════════════════════════════════
# MyBiz.Fit Tenant Configuration Generator
# Instructions: Fill in each field. Replace null with actual values.
# Agents: Read this file as your template, output a valid tenant YAML.
# ═══════════════════════════════════════════════════════════════

# ── STEP 1: Business Type ──
# Choose one: restaurant | salon | contractor | dental | real-estate | fitness | retail | professional
# This determines which sections are enabled by default.
businessType: null

# ── STEP 2: Theme ──
# Choose one:
#   Dark: dark-amber | dark-ocean | dark-forest | dark-rose
#   Light: light-minimal | light-ocean | light-warm | light-lavender
theme: null

# ── STEP 3: Accent Override (optional) ──
# null = use theme default. Set a hex color to override.
# Example: "#e63946"
accent: null

# ── STEP 4: Business Information ──
business:
  name: null                    # Business name (required)
  tagline: null                 # Short headline for hero (e.g., "Fresh Pizza, Delivered Fast")
  description: null             # 1-2 sentence description of the business
  heroImage: null               # Path to hero image in /static/ (e.g., "/joes-pizza/images/hero.jpg")

# ── STEP 5: Section Toggles ──
# Set each to true/false based on what the business needs.
sections:
  hero: true
  services: true                # Menu items, service offerings, etc.
  video: false                  # YouTube embed
  gallery: false                # Photo gallery
  about: true                   # About section
  contact: true                 # Contact info cards
  testimonials: false           # Customer reviews
  branches: false               # Multiple locations
  products: false               # Product catalog with images
  cta: true                     # Call-to-action banner

# ── STEP 6: Section Data ──
# Only fill in sections that are set to true above.

# Services / Menu Items / Offerings
services: []
  # - name: "Service Name"
  #   description: "Short description"
  #   icon: "πŸ”§"

# Video (YouTube)
# video:
#   youtubeId: "dQw4w9WgXcQ"    # 11-character YouTube video ID
#   title: "Video Title"

# Photo Gallery (array of image paths)
gallery: []
  # - "/business-name/images/photo1.jpg"

# About Section
# about:
#   title: "About Our Business"
#   description: "Longer description of the business..."
#   image: "/business-name/images/about.jpg"

# Branches / Locations
branches: []
  # - name: "Main Location"
  #   address: "123 Main St, City, State ZIP"
  #   phone: "(555) 123-4567"
  #   mapUrl: "https://maps.google.com/?q=123+Main+St"

# Products (with images)
products: []
  # - name: "Product Name"
  #   price: "$29.99"
  #   description: "Product description"
  #   image: "/business-name/images/product1.jpg"

# Call-to-Action
cta:
  text: null                    # CTA headline (e.g., "Ready to Order?")
  button: "Chat with Us"       # Button label
  action: "chat"               # "chat" = opens widget, or a URL

# Contact Information
contact:
  address: null
  phone: null
  email: null
  hours: null                  # e.g., "Mon-Fri 9am-5pm, Sat 10am-2pm"

# Customer Reviews
testimonials: []
  # - name: "Customer Name"
  #   text: "What they said..."
  #   rating: 5

# ── STEP 7: Chat Widget Config ──
chat:
  welcome: "Hi! How can I help you today?"
  companyName: null             # null = use business.name

# ── STEP 8: Special Requests ──
# Free-form narrative for anything not covered above.
# Use this for: custom fonts, specific layout changes, unique sections,
# brand guidelines, or any other special requirements.
specialRequests: |
  null

Step 7: Implementation Order

# Task Files Effort Dependencies
1 Create 8 theme presets data/themes/*.yaml Small None
2 Create 8 business templates data/templates/*.yaml Medium None
3 Refactor landing.css β€” replace hardcoded colors static/css/landing.css Small None
4 Update list.html β€” theme loading + CSS var injection layouts/_default/list.html Medium Steps 1, 3
5 Update baseof.html β€” no changes needed (list.html handles it) β€” Skip β€”
6 Update rgresidences.yaml β€” add theme field data/tenants/rgresidences.yaml Tiny Step 1
7 Create prompt template prompts/tenant-config.yaml Small Steps 1, 2
8 Test build β€” Medium All

Step 8: Testing

  1. Run hugo server in mybiz/
  2. Verify homepage renders with default brand theme (unchanged)
  3. Verify rgresidences tenant page renders with dark-amber theme
  4. Temporarily change rgresidences.yaml to theme: "dark-ocean" β€” verify accent changes
  5. Temporarily change to theme: "light-ocean" β€” verify light mode renders correctly
  6. Test accent override: set accent: "#ff0000" β€” verify it overrides the theme
  7. Verify all 8 theme presets render without CSS errors
  8. Verify chat widget picks up the tenant accent color

End-to-End Workflow

For you (business owner):

  1. Pick a business template: cp data/templates/restaurant.yaml data/tenants/joes-pizza.yaml
  2. Edit the YAML: fill in business name, description, services, contact info
  3. Pick a theme: theme: "dark-amber" (or any preset)
  4. Run hugo β†’ page is live at mybiz.fit/joes-pizza

For coding agents:

  1. Receive user's business description
  2. Read prompts/tenant-config.yaml as instructions
  3. Auto-fill all fields based on business type + user preferences
  4. Write tenant YAML + content _index.md
  5. Done β€” page is live

For tenants (after signup via chat):

  1. Chat widget collects business info
  2. You (or an agent) generate the tenant config from the prompt template
  3. Tenant gets their link: mybiz.fit/<their-business>