PDF Edge-to-Edge Backgrounds & Brand Config Reorganization
Solved a critical PDF rendering issue where page backgrounds didn't extend to all edges and content lacked proper per-page spacing. Discovered WeasyPrint's `@page background-color` property enables edge-to-edge colored backgrounds while maintaining proper content margins on every…
Changelog - 2025-11-18_03
PDF Edge-to-Edge Backgrounds & Brand Config Reorganization
Date: November 18, 2025 Type: Fix (Critical) + Improvement Impact: Major - enables professional PDF exports with proper branding
Summary
Solved a critical PDF rendering issue where page backgrounds didn't extend to all edges and content lacked proper per-page spacing. Discovered WeasyPrint's @page background-color property enables edge-to-edge colored backgrounds while maintaining proper content margins on every page. Also refactored CSS architecture to be brand-agnostic and reorganized brand configurations into a dedicated directory for better organization.
Motivation
The Problem: When exporting branded memos to PDF (especially in dark mode), we encountered multiple issues:
Content touching the top of pages 2+ (no padding)
Using CSS margins created white borders (not brand-colored)
Body padding only applied to page 1, not subsequent pages
No clean way to have colored backgrounds extend to all edges while maintaining content spacing
The Impact:
PDFs looked unprofessional with inconsistent spacing
Dark mode exports had distracting white margins
Couldn't deliver branded PDFs to clients
WeasyPrint's pagination behavior was poorly understood
What Changed
1. PDF Page Background & Spacing Fix (CRITICAL)
The Discovery:
After extensive research into WeasyPrint documentation, discovered that @page rules support a background-color property that colors the entire page including margin areas, while @page margin creates a proper content area on each page.
Before (Broken):
/* Tried body padding - only applies to page 1 */
body {
padding: 0.75in 0.75in 0.5in 0.75in;
}
/* Tried margins - creates WHITE borders */
@page {
margin: 0.75in 0.75in 0.5in 0.75in; /* White margins! */
}After (Working):
@page {
size: letter;
margin: 0.75in 0.75in 0.5in 0.75in; /* Content area on each page */
background-color: #203e4b; /* Colors entire page edge-to-edge */
}
/* Dark mode uses primary color for page background */
@page {
background-color: var(--brand-primary); /* Navy background */
}
/* Light mode uses background color for page background */
@page {
background-color: var(--brand-background); /* White background */
}Dynamic Color Injection:
def generate_css_from_brand(brand: BrandConfig, base_css_path: Path, dark_mode: bool = False):
# Determine page background based on mode
page_bg = brand.colors.primary if dark_mode else brand.colors.background
# Replace all @page background-color instances
css_content = css_content.replace(
'background-color: #ffffff; /* Light mode background - will be overridden in dark mode */',
f'background-color: {page_bg};'
)Result:
✅ Edge-to-edge colored backgrounds on every page
✅ Proper content spacing (0.75in margins) on every page
✅ No white borders in dark mode
✅ Professional, branded PDFs ready for client delivery
2. Brand-Agnostic CSS Architecture
Problem:
CSS file named
hypernova-style.css(brand-specific)CSS variables named
--hypernova-navy,--hypernova-cyan(hardcoded)Future CSS improvements would need to be duplicated for each brand
Solution: Renamed and refactored CSS to be brand-agnostic:
File Rename:
templates/hypernova-style.css → templates/base-style.cssCSS Variable Refactoring:
/* OLD - brand-specific */
:root {
--hypernova-navy: #1a3a52;
--hypernova-cyan: #1dd3d3;
--hypernova-dark: #1a2332;
--hypernova-gray: #6b7280;
}
/* NEW - brand-agnostic */
:root {
--brand-primary: #1a3a52;
--brand-secondary: #1dd3d3;
--brand-text-dark: #1a2332;
--brand-text-light: #6b7280;
--brand-background: #ffffff;
--brand-background-alt: #f0f0eb;
}Benefits:
Single CSS template serves all brands
CSS improvements automatically apply to all brands
Clear, semantic variable names
No brand-specific CSS files needed
Files Modified:
templates/base-style.css(renamed from hypernova-style.css)export-branded.py(updated to reference base-style.css)All brand configs use same base template
3. Brand Configuration Directory Reorganization
Problem:
Brand config files scattered in project root
Cluttered root directory with 4+ YAML files
Not obvious where to put new brand configs
Solution: Created dedicated directory structure:
templates/
├── brand-configs/ # NEW - centralized brand configs
│ ├── brand-accel-config.yaml
│ ├── brand-collide-config.yaml
│ ├── brand-config.example.yaml
│ ├── brand-hypernova-config.yaml
├── fonts/
│ ├── Arboria/
│ ├── GeneralSans/
│ └── Ranade/
└── base-style.cssCode Updates:
# src/branding.py - updated loading logic
if brand_name:
# Look in templates/brand-configs/ first, then root directory
config_path = Path(f"templates/brand-configs/brand-{brand_name}-config.yaml")
if not config_path.exists():
config_path = Path(f"brand-{brand_name}-config.yaml") # Backward compat
else:
# Look for default config in templates/brand-configs/ first
config_path = Path("templates/brand-configs/brand-config.yaml")
if not config_path.exists():
config_path = Path("brand-config.yaml") # Backward compatBenefits:
✅ Cleaner project root
✅ All brand configs in one place
✅ Easier to find and manage multiple brands
✅ Backward compatible with root directory configs
✅ Follows standard template organization pattern
4. Documentation Updates
README.md:
Added "Creating Your Own Brand" section with 3-step guide
Updated all paths to reference
templates/brand-configs/Simplified language for VC firms to understand setup
docs/CUSTOM-BRANDING.md:
Updated Quick Start with new directory paths
Updated all command examples
Updated Troubleshooting section
Updated Best Practices
Updated Multiple Brands Management section
src/branding.py:
Updated
list_available_brands()to search new directoryFixed default Hypernova config to point to
templates/fonts/ArboriaUpdated docstrings with new directory structure
Technical Details
WeasyPrint @page Behavior
Key Discovery:
WeasyPrint implements CSS Paged Media Module Level 3, where @page rules define properties for each page box, not just the first page.
Critical Properties:
@page { size: letter; }- Defines page dimensions@page { margin: <values>; }- Creates content area on each page (not white margin!)@page { background-color: <color>; }- Colors entire page including margin areas
Why This Works:
margincreates a content box on each page (automatic pagination)background-colorapplies to page box (full bleed to edges)Content automatically flows with proper spacing on every page
No need for manual page break handling
Mode-Specific Implementation:
/* Light mode - white page background */
@page {
background-color: var(--brand-background); /* Injected: #ffffff */
}
/* Dark mode - navy page background */
@page {
background-color: var(--brand-primary); /* Injected: #1a3a52 */
}Files Modified
Modified:
templates/base-style.css(renamed, +20 lines for @page background)export-branded.py(+30 lines for @page background injection)src/branding.py(+20 lines for new directory structure)README.md(+35 lines for simplified brand creation guide)docs/CUSTOM-BRANDING.md(~15 lines updated paths)context-vigilance/Multi-Agent-Orchestration...md(+80 lines documenting PDF solution)
Moved:
brand-accel-config.yaml→templates/brand-configs/brand-collide-config.yaml→templates/brand-configs/brand-config.example.yaml→templates/brand-configs/brand-hypernova-config.yaml→templates/brand-configs/
Testing
PDF Export Testing:
✅ Dark mode Collide branding: Edge-to-edge navy background, proper spacing
✅ Light mode Hypernova branding: Edge-to-edge white background, proper spacing
✅ Multi-page documents: Spacing consistent across all pages
✅ No white margins: Background color extends to all edges
✅ Content area: 0.75in margins maintained on all pages
Brand Config Testing:
✅ Load from new directory:
BrandConfig.load(brand_name='collide')✅ Backward compatibility: Still checks root directory if not found
✅ List available brands: Finds all 3 brands (accel, collide, hypernova)
✅ Export commands: All
--brandflags work as expected
Export Results:
Bear AI memo (dark mode, Collide): 57.6 KB HTML, professional PDF
Content properly spaced on all 8+ pages
No white margins in dark mode
Edge-to-edge navy background (#203e4b)
Benefits
1. Professional PDF Exports
Client-ready PDFs with proper branding
Edge-to-edge colored backgrounds
Consistent spacing on every page
No visual artifacts or white margins
2. Better Code Organization
Brand-agnostic CSS (one template for all brands)
Centralized brand configuration directory
Cleaner project root
Easier to maintain and extend
3. Improved Documentation
Simple 3-step guide for VC firms
Clear examples and explanations
Troubleshooting guidance
Updated for new directory structure
4. Developer Experience
Future CSS improvements apply to all brands automatically
Clear separation of concerns (base CSS vs brand configs)
Easy to add new brands
Backward compatible (no breaking changes)
Lessons Learned
Read the Documentation: WeasyPrint's
@page background-colorwas documented but not obvious. Deep research into CSS Paged Media spec was critical.CSS Paged Media ≠ Screen CSS: Pagination behavior requires different mental model. Body padding doesn't paginate, but @page margins do.
Test with Real Content: Multi-page documents revealed issues that single-page tests didn't show.
Avoid Repeated Mistakes: Initially tried margin/padding combinations multiple times before researching the library properly.
Brand-Agnostic Design: Single source of truth (base-style.css) makes maintenance far easier than per-brand CSS files.
Known Limitations
WeasyPrint Only: Solution works with WeasyPrint, may not work with other PDF generators
CSS Paged Media Support: Requires modern CSS Paged Media implementation
No Print Stylesheet Separation: @page rules in main CSS (could be separated for cleaner code)
Future Enhancements
Separate print-specific CSS into dedicated file
Add @page size options (A4, legal, custom)
Support for headers/footers in @page rules
Page numbering via @page counters
Different @page rules for first/last pages
Related Work
This Session Also Completed:
Bear AI memo generation (scored 6.5/10, saved for review)
Collide Capital branded export testing
Documentation of PDF solution in context-vigilance document
Related Changelogs:
2025-11-18_02.md - Multi-brand export system
2025-11-17_01.md - Dark mode implementation
Migration Guide
No Action Required - all changes are backward compatible.
To Use New Features:
Create branded PDFs:
BASHpython export-branded.py memo.md --brand collide --mode dark --pdfMove old brand configs (optional):
BASHmv brand-*-config.yaml templates/brand-configs/Verify edge-to-edge backgrounds:
Open PDF in viewer
Check all pages have colored backgrounds
Verify no white margins at edges
Credits
Implementation: Claude Code (Sonnet 4.5) Direction & Testing: Michael Staton Key Insight: WeasyPrint documentation deep-dive after initial failures Time Investment: ~1 hour troubleshooting → breakthrough with @page research
Impact Summary:
✅ Professional PDF exports now possible
✅ Brand system more maintainable and extensible
✅ Better organized project structure
✅ Clearer documentation for VC firms
✅ Zero breaking changes