Lossless Changelog
component

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:

  1. Content touching the top of pages 2+ (no padding)

  2. Using CSS margins created white borders (not brand-colored)

  3. Body padding only applied to page 1, not subsequent pages

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

CSS
/* 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):

CSS
@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:

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

BASH
templates/hypernova-style.css → templates/base-style.css

CSS Variable Refactoring:

CSS
/* 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.css

Code Updates:

PYTHON
# 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 compat

Benefits:

  • ✅ 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 directory

  • Fixed default Hypernova config to point to templates/fonts/Arboria

  • Updated 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:

  1. margin creates a content box on each page (automatic pagination)

  2. background-color applies to page box (full bleed to edges)

  3. Content automatically flows with proper spacing on every page

  4. No need for manual page break handling

Mode-Specific Implementation:

CSS
/* 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:

  1. ✅ Dark mode Collide branding: Edge-to-edge navy background, proper spacing

  2. ✅ Light mode Hypernova branding: Edge-to-edge white background, proper spacing

  3. ✅ Multi-page documents: Spacing consistent across all pages

  4. ✅ No white margins: Background color extends to all edges

  5. ✅ Content area: 0.75in margins maintained on all pages

Brand Config Testing:

  1. ✅ Load from new directory: BrandConfig.load(brand_name='collide')

  2. ✅ Backward compatibility: Still checks root directory if not found

  3. ✅ List available brands: Finds all 3 brands (accel, collide, hypernova)

  4. ✅ Export commands: All --brand flags 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

  1. Read the Documentation: WeasyPrint's @page background-color was documented but not obvious. Deep research into CSS Paged Media spec was critical.

  2. CSS Paged Media ≠ Screen CSS: Pagination behavior requires different mental model. Body padding doesn't paginate, but @page margins do.

  3. Test with Real Content: Multi-page documents revealed issues that single-page tests didn't show.

  4. Avoid Repeated Mistakes: Initially tried margin/padding combinations multiple times before researching the library properly.

  5. Brand-Agnostic Design: Single source of truth (base-style.css) makes maintenance far easier than per-brand CSS files.

Known Limitations

  1. WeasyPrint Only: Solution works with WeasyPrint, may not work with other PDF generators

  2. CSS Paged Media Support: Requires modern CSS Paged Media implementation

  3. 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

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:

Migration Guide

No Action Required - all changes are backward compatible.

To Use New Features:

  1. Create branded PDFs:

    BASH
    python export-branded.py memo.md --brand collide --mode dark --pdf
  2. Move old brand configs (optional):

    BASH
    mv brand-*-config.yaml templates/brand-configs/
  3. 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