Agentman PDF Style Guide
When to Use
Load this skill before writing any PDF generation code — whether using reportlab, fpdf2, or weasyprint. This skill provides the complete Agentman brand implementation for PDF output.
The brand identity is quiet confidence — charcoal suits, not fire trucks. Carbon and warm neutrals do the heavy lifting. Terracotta appears as a rare, deliberate accent.
STOP — Read Before Writing Any Code
Before writing a SINGLE line of PDF generation code, you MUST:
- Define ALL color constants below as the first lines of your script
- Set document author to
"Agentman Equity Research Assistant" - Set document title to
"Agentman — {Report Title}" - Display author on page 1 immediately after the report title:
"Author: Agentman Equity Research Assistant" - NEVER use library default colors — override every color explicitly
- NEVER import
colors.black,colors.grey,colors.blue,colors.navy,colors.darkblue,colors.lightgreyfrom reportlab - NEVER use
getSampleStyleSheet()without overriding every color
Visual Weight Distribution
Every report must maintain this ratio:
- 70% Carbon/Charcoal (
#141413,#292322,#3D3735) — the backbone - 20% Warm Neutrals (
#F0EEE6,#F6EAE6,#E3DACC,#FAF9F5,#FFFFFF) — warmth without color - 10% Terracotta Accent (
#CC785C,#D97757,#A65945) — sparingly, max 3 per section
reportlab Implementation
Color Constants (Copy-Paste This Exactly)
from reportlab.lib.colors import HexColor
# ============================================================
# AGENTMAN BRAND COLORS — MANDATORY for all PDF reports
# ============================================================
# Primary Brand (Terracotta) — use sparingly, max 3 per section
AGENTMAN_800 = HexColor('#703B2D') # Badge text, deep emphasis (rare)
AGENTMAN_700 = HexColor('#8B4A38') # Cover accent, metric box values (rare)
AGENTMAN_600 = HexColor('#A65945') # Negative emphasis text
AGENTMAN_500 = HexColor('#CC785C') # PRIMARY ACCENT — stat numbers, table header text, accent lines
AGENTMAN_400 = HexColor('#D97757') # Highlight/caution text
AGENTMAN_200 = HexColor('#E6A890') # Badge borders
AGENTMAN_150 = HexColor('#E3DACC') # TABLE BORDERS, separators
AGENTMAN_100 = HexColor('#F6EAE6') # TABLE HEADER FILL (light warm tint)
AGENTMAN_75 = HexColor('#F0EEE6') # Highlight row bg, callout blocks
AGENTMAN_50 = HexColor('#FAF9F5') # Alternate row bg (cream)
# Text Colors (Charcoal) — 70% of visual weight
CHARCOAL_950 = HexColor('#141413') # PRIMARY BODY TEXT
CHARCOAL_900 = HexColor('#292322') # Section titles, cover title
CHARCOAL_800 = HexColor('#3D3735') # Secondary text
# Neutral (Slate)
SLATE_700 = HexColor('#334155') # Secondary text on very light backgrounds
SLATE_600 = HexColor('#475569') # Secondary body text, metric labels
SLATE_500 = HexColor('#64748B') # Footer, muted captions
SLATE_400 = HexColor('#94A3B8') # Descriptions, meta text
WHITE = HexColor('#FFFFFF')
# ============================================================
# BANNED — NEVER import or use these:
# colors.black, colors.grey, colors.blue, colors.lightgrey
# colors.darkblue, colors.navy, colors.steelblue
# HexColor('#000000'), HexColor('#333333'), HexColor('#666666')
# Any hex not listed above
# ============================================================
Table Style (Copy-Paste This Exactly)
from reportlab.platypus import Table, TableStyle
def agentman_table_style():
"""Return the MANDATORY Agentman table style. Use for ALL tables."""
return TableStyle([
# Header row — LIGHT warm fill + terracotta text
('BACKGROUND', (0, 0), (-1, 0), AGENTMAN_100), # #F6EAE6 light fill
('TEXTCOLOR', (0, 0), (-1, 0), AGENTMAN_500), # #CC785C terracotta text
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 11),
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
('TOPPADDING', (0, 0), (-1, 0), 8),
# Body rows — charcoal text
('TEXTCOLOR', (0, 1), (-1, -1), CHARCOAL_950), # #141413 body text
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 10),
('BOTTOMPADDING', (0, 1), (-1, -1), 6),
('TOPPADDING', (0, 1), (-1, -1), 6),
# First column — bold labels (same color as body, NOT dark fill)
('FONTNAME', (0, 1), (0, -1), 'Helvetica-Bold'),
# Alternating row backgrounds
('ROWBACKGROUNDS', (0, 1), (-1, -1), [WHITE, AGENTMAN_50]),
# Grid — warm brand borders
('GRID', (0, 0), (-1, -1), 0.5, AGENTMAN_150), # #E3DACC warm
# Alignment
('ALIGN', (0, 0), (-1, 0), 'CENTER'), # Header centered
('ALIGN', (0, 1), (0, -1), 'LEFT'), # First col left
('ALIGN', (1, 1), (-1, -1), 'CENTER'), # Data centered
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
])
def agentman_highlight_table_style():
"""Table style variant with highlight rows for totals/summaries."""
base = agentman_table_style()
# Add highlight row styling — apply to specific rows after creation:
# table_style.add('BACKGROUND', (0, row_idx), (-1, row_idx), AGENTMAN_75)
return base
Document Setup (Copy-Paste This Exactly)
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, PageBreak
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib.enums import TA_LEFT, TA_CENTER
def create_agentman_doc(filename, title, date_str):
"""Create an Agentman-branded PDF document."""
doc = SimpleDocTemplate(filename, pagesize=letter,
leftMargin=0.75*inch, rightMargin=0.75*inch,
topMargin=0.75*inch, bottomMargin=0.75*inch,
title=f"Agentman — {title}",
author="Agentman Equity Research Assistant")
return doc
Paragraph Styles (Copy-Paste This Exactly)
# ============================================================
# AGENTMAN PARAGRAPH STYLES — use these for ALL text elements
# ============================================================
STYLE_TITLE = ParagraphStyle('AgentmanTitle',
fontName='Helvetica-Bold', fontSize=24, textColor=CHARCOAL_900,
spaceAfter=4, leading=30, alignment=TA_LEFT)
STYLE_SUBTITLE = ParagraphStyle('AgentmanSubtitle',
fontName='Helvetica', fontSize=13, textColor=SLATE_600,
spaceBefore=2, spaceAfter=6, leading=17, alignment=TA_LEFT)
STYLE_AUTHOR = ParagraphStyle('AgentmanAuthor',
fontName='Helvetica', fontSize=11, textColor=SLATE_600,
spaceAfter=12, leading=14, alignment=TA_LEFT)
STYLE_SECTION = ParagraphStyle('AgentmanSection',
fontName='Helvetica-Bold', fontSize=16, textColor=CHARCOAL_900,
spaceBefore=18, spaceAfter=6, alignment=TA_LEFT)
STYLE_SUBSECTION = ParagraphStyle('AgentmanSubsection',
fontName='Helvetica-Bold', fontSize=13, textColor=CHARCOAL_900,
spaceBefore=12, spaceAfter=4, alignment=TA_LEFT)
STYLE_BODY = ParagraphStyle('AgentmanBody',
fontName='Helvetica', fontSize=11, textColor=CHARCOAL_950,
spaceAfter=8, leading=15, alignment=TA_LEFT)
STYLE_BODY_SECONDARY = ParagraphStyle('AgentmanBodySecondary',
fontName='Helvetica', fontSize=10, textColor=SLATE_600,
spaceAfter=6, leading=14, alignment=TA_LEFT)
STYLE_STAT_NUMBER = ParagraphStyle('AgentmanStatNumber',
fontName='Helvetica-Bold', fontSize=28, textColor=AGENTMAN_500,
spaceBefore=6, spaceAfter=2, leading=34, alignment=TA_CENTER)
STYLE_STAT_LABEL = ParagraphStyle('AgentmanStatLabel',
fontName='Helvetica', fontSize=10, textColor=SLATE_600,
spaceBefore=0, spaceAfter=8, leading=13, alignment=TA_CENTER)
STYLE_FOOTER = ParagraphStyle('AgentmanFooter',
fontName='Helvetica-Oblique', fontSize=8, textColor=SLATE_500,
alignment=TA_CENTER)
STYLE_DISCLAIMER = ParagraphStyle('AgentmanDisclaimer',
fontName='Helvetica-Oblique', fontSize=8, textColor=SLATE_500,
spaceAfter=4, leading=11, alignment=TA_CENTER)
STYLE_CALLOUT = ParagraphStyle('AgentmanCallout',
fontName='Helvetica', fontSize=11, textColor=CHARCOAL_950,
spaceAfter=8, leading=15, alignment=TA_LEFT,
backColor=AGENTMAN_75, borderPadding=12)
Stat Block Layout (MANDATORY — Prevents Overlap)
CRITICAL: Never place stat blocks as consecutive centered paragraphs. Large font stat numbers (28pt) will overlap their labels and adjacent stats. Always use a Table to contain side-by-side stat blocks.
def agentman_stat_row(stat_pairs, page_width=7.0*inch):
"""Build a row of stat blocks using a Table to prevent overlap.
Args:
stat_pairs: list of (value, label) tuples, e.g. [("$264.35", "AAPL Price"), ...]
page_width: available content width (letter page minus margins)
Returns:
A Table flowable with stat blocks that will NOT overlap.
"""
n = len(stat_pairs)
col_w = page_width / n
# Row 1: stat numbers (large terracotta)
# Row 2: stat labels (small slate)
data = [
[Paragraph(val, STYLE_STAT_NUMBER) for val, _ in stat_pairs],
[Paragraph(lbl, STYLE_STAT_LABEL) for _, lbl in stat_pairs],
]
t = Table(data, colWidths=[col_w] * n)
t.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('TOPPADDING', (0, 0), (-1, 0), 10),
('BOTTOMPADDING', (0, 0), (-1, 0), 2),
('TOPPADDING', (0, 1), (-1, 1), 0),
('BOTTOMPADDING', (0, 1), (-1, 1), 10),
('GRID', (0, 0), (-1, -1), 0, WHITE), # invisible grid
]))
return t
# Usage example — NEVER do Paragraph+Paragraph stacking for stats:
# WRONG (causes overlap):
# elements.append(Paragraph("$264.35", STYLE_STAT_NUMBER))
# elements.append(Paragraph("AAPL Price", STYLE_STAT_LABEL))
#
# CORRECT:
# elements.append(agentman_stat_row([
# ("$264.35", "AAPL Price"),
# ("$303.33", "GOOGL Price"),
# ]))
Section Divider (Terracotta Accent Line)
from reportlab.platypus import HRFlowable
def agentman_section_divider():
"""Create a 3pt terracotta accent line between sections."""
return HRFlowable(
width="100%", thickness=3,
color=AGENTMAN_500, spaceBefore=6, spaceAfter=12
)
def agentman_thin_divider():
"""Create a 1pt warm separator."""
return HRFlowable(
width="100%", thickness=1,
color=AGENTMAN_150, spaceBefore=4, spaceAfter=8
)
Page 1 Template (Mandatory First-Page Content)
def build_cover_page(elements, report_title, date_str, subtitle=None):
"""Build the mandatory Agentman cover page content.
Args:
elements: list of flowables to append to
report_title: main title (will be prefixed with "Agentman —")
date_str: date string for the report
subtitle: optional subtitle (e.g. "Stock Comparison Report")
"""
# Title — must start with "Agentman —"
elements.append(Paragraph(f"Agentman — {report_title}", STYLE_TITLE))
# Subtitle — if provided, uses smaller font with clear spacing
if subtitle:
elements.append(Paragraph(subtitle, STYLE_SUBTITLE))
# Author — MUST appear immediately after title, non-negotiable
elements.append(Paragraph("Author: Agentman Equity Research Assistant", STYLE_AUTHOR))
# Date and data source
elements.append(Paragraph(f"{date_str} | Data Source: Financial Modeling Prep", STYLE_AUTHOR))
# Terracotta accent line
elements.append(agentman_section_divider())
return elements
Footer (Apply to Every Page Except Cover)
from reportlab.platypus import Frame, PageTemplate
from reportlab.lib.units import inch
from datetime import date
def agentman_footer(canvas, doc):
"""Draw Agentman footer on every page except the first."""
if doc.page > 1:
canvas.saveState()
# Footer rule — terracotta accent line
canvas.setStrokeColor(AGENTMAN_500)
canvas.setLineWidth(1)
canvas.line(0.75*inch, 0.6*inch, doc.width + 0.75*inch, 0.6*inch)
# Footer text
canvas.setFont('Helvetica', 8)
canvas.setFillColor(SLATE_500)
footer_text = f"Agentman Equity Research Assistant · {date.today().strftime('%B %d, %Y')} · Data: FMP"
canvas.drawCentredString(doc.width/2 + 0.75*inch, 0.4*inch, footer_text)
# Page number
canvas.drawRightString(doc.width + 0.75*inch, 0.4*inch, f"{doc.page}")
canvas.restoreState()
Chart Colors (matplotlib)
import matplotlib
import matplotlib.pyplot as plt
# Agentman chart palette — warm, monochromatic progression
CHART_COLORS = [
'#CC785C', # AGENTMAN_500 — primary series
'#292322', # CHARCOAL_900 — secondary series
'#E3DACC', # AGENTMAN_150 — tertiary series
'#A65945', # AGENTMAN_600 — fourth series
'#8B4A38', # AGENTMAN_700 — fifth series
'#D97757', # AGENTMAN_400 — sixth series
'#475569', # SLATE_600 — seventh series
'#703B2D', # AGENTMAN_800 — eighth series
]
def apply_agentman_chart_style():
"""Apply Agentman brand colors to matplotlib globally."""
matplotlib.rcParams['axes.prop_cycle'] = matplotlib.cycler(color=CHART_COLORS)
matplotlib.rcParams['axes.edgecolor'] = '#292322'
matplotlib.rcParams['axes.labelcolor'] = '#141413'
matplotlib.rcParams['xtick.color'] = '#475569'
matplotlib.rcParams['ytick.color'] = '#475569'
matplotlib.rcParams['grid.color'] = '#E3DACC'
matplotlib.rcParams['figure.facecolor'] = '#FFFFFF'
matplotlib.rcParams['font.family'] = 'sans-serif'
matplotlib.rcParams['font.sans-serif'] = ['Calibri', 'Helvetica', 'Arial']
fpdf2 Implementation
Color Constants (Copy-Paste This Exactly)
from fpdf import FPDF
# ============================================================
# AGENTMAN BRAND COLORS — fpdf2 RGB tuples
# ============================================================
AGENTMAN_800_RGB = (112, 59, 45) # Badge text, deep emphasis
AGENTMAN_700_RGB = (139, 74, 56) # Cover accent
AGENTMAN_600_RGB = (166, 89, 69) # Negative emphasis
AGENTMAN_500_RGB = (204, 120, 92) # PRIMARY ACCENT
AGENTMAN_400_RGB = (217, 119, 87) # Highlight text
AGENTMAN_200_RGB = (230, 168, 144) # Badge borders
AGENTMAN_150_RGB = (227, 218, 204) # Table borders
AGENTMAN_100_RGB = (246, 234, 230) # Table header fill
AGENTMAN_75_RGB = (240, 238, 230) # Highlight row bg
AGENTMAN_50_RGB = (250, 249, 245) # Alternate row bg
CHARCOAL_950_RGB = (20, 20, 19) # Body text
CHARCOAL_900_RGB = (41, 35, 34) # Section titles
CHARCOAL_800_RGB = (61, 55, 53) # Secondary text
SLATE_600_RGB = (71, 85, 105) # Secondary text
SLATE_500_RGB = (100, 116, 139) # Footer text
WHITE_RGB = (255, 255, 255)
fpdf2 Table Helper
def fpdf2_draw_agentman_table(pdf, headers, data, col_widths=None):
"""Draw an Agentman-styled table using fpdf2."""
if col_widths is None:
col_widths = [pdf.epw / len(headers)] * len(headers)
# Header row — light warm fill + terracotta text
pdf.set_fill_color(*AGENTMAN_100_RGB)
pdf.set_text_color(*AGENTMAN_500_RGB)
pdf.set_draw_color(*AGENTMAN_150_RGB)
pdf.set_font('Helvetica', 'B', 11)
for i, header in enumerate(headers):
pdf.cell(col_widths[i], 10, header, border=1, fill=True, align='C')
pdf.ln()
# Body rows — alternating backgrounds
pdf.set_font('Helvetica', '', 10)
for row_idx, row in enumerate(data):
if row_idx % 2 == 0:
pdf.set_fill_color(*WHITE_RGB)
else:
pdf.set_fill_color(*AGENTMAN_50_RGB)
pdf.set_text_color(*CHARCOAL_950_RGB)
for i, cell in enumerate(row):
font_style = 'B' if i == 0 else ''
pdf.set_font('Helvetica', font_style, 10)
pdf.cell(col_widths[i], 9, str(cell), border=1, fill=True,
align='L' if i == 0 else 'C')
pdf.ln()
weasyprint Implementation
Complete CSS Stylesheet (Copy-Paste This Exactly)
/* ============================================================
AGENTMAN BRAND STYLESHEET — weasyprint
Apply to ALL HTML before converting to PDF
============================================================ */
@page {
size: letter;
margin: 0.75in;
@bottom-center {
content: "Agentman Equity Research Assistant · Data: FMP";
font-family: Calibri, 'Segoe UI', sans-serif;
font-size: 8pt;
color: #64748B;
}
@bottom-right {
content: counter(page);
font-family: Calibri, 'Segoe UI', sans-serif;
font-size: 8pt;
color: #64748B;
}
}
* { font-family: Calibri, 'Segoe UI', sans-serif; box-sizing: border-box; }
body { color: #141413; font-size: 11pt; line-height: 1.4; }
/* HEADINGS — charcoal, NEVER colored */
h1 { color: #292322; font-size: 24pt; font-weight: bold; margin-bottom: 4pt; }
h2 { color: #292322; font-size: 18pt; font-weight: bold; border-bottom: 3px solid #CC785C; padding-bottom: 4pt; margin-top: 18pt; }
h3 { color: #292322; font-size: 14pt; font-weight: bold; border-bottom: 2px solid #CC785C; padding-bottom: 3pt; margin-top: 14pt; }
h4 { color: #3D3735; font-size: 12pt; font-weight: bold; margin-top: 10pt; }
/* AUTHOR LINE — must appear on page 1 */
.author { color: #475569; font-size: 11pt; margin-bottom: 12pt; }
.date { color: #475569; font-size: 11pt; margin-bottom: 16pt; }
/* TABLE HEADERS — CRITICAL: light fill + terracotta text, NEVER dark */
table { border-collapse: collapse; width: 100%; margin: 12pt 0; }
th, thead td {
background-color: #F6EAE6 !important;
color: #CC785C !important;
font-weight: 600;
font-size: 11pt;
padding: 9px 14px;
border-bottom: 1px solid #E3DACC;
text-align: center;
}
/* TABLE BODY */
td {
color: #141413;
padding: 9px 14px;
border-bottom: 1px solid #E3DACC;
font-size: 10pt;
}
td:first-child { font-weight: 600; color: #141413; text-align: left; }
tr:nth-child(odd) td { background-color: #FFFFFF; }
tr:nth-child(even) td { background-color: #FAF9F5; }
tr.highlight td { background-color: #F0EEE6; }
/* STAT BLOCKS — use .stat-row container to prevent overlap */
.stat-row {
display: flex;
justify-content: space-evenly;
align-items: flex-start;
gap: 16px;
margin: 18pt 0;
}
.stat-block {
flex: 1;
text-align: center;
padding: 12px 8px;
min-width: 0; /* prevent flex overflow */
}
.stat-number {
font-size: 28pt;
font-weight: bold;
color: #CC785C;
text-align: center;
line-height: 1.2; /* CRITICAL: prevents overlap with label */
margin-bottom: 4pt;
}
.stat-label {
font-size: 10pt;
color: #475569;
text-align: center;
line-height: 1.3;
margin-top: 0;
}
/* NEVER place .stat-number and .stat-label as siblings outside a .stat-block container */
/* EMPHASIS — terracotta family only, NEVER green/red/blue */
.positive { color: #CC785C; font-weight: 700; }
.negative { color: #A65945; font-weight: 700; }
.highlight-text { color: #D97757; font-weight: 600; }
.neutral { color: #141413; font-weight: 600; }
/* CALLOUT BLOCKS */
.callout { background: #F0EEE6; border-radius: 4px; padding: 16px 20px; margin: 12pt 0; }
.callout h3, .callout h4 { color: #141413; font-weight: bold; border: none; margin-top: 0; }
.callout p { color: #475569; }
/* BADGES — all same color, tier via text wording */
.badge {
display: inline-block;
background: #F6EAE6; color: #703B2D;
border: 1px solid #E6A890;
padding: 3px 10px; border-radius: 4px;
font-size: 10pt; font-weight: 600;
}
/* SCORE BARS */
.score-bar { background: #E3DACC; height: 6px; border-radius: 3px; width: 100%; }
.score-fill { background: #CC785C; height: 6px; border-radius: 3px; }
/* FOOTER */
footer, .footer { font-size: 8pt; color: #64748B; text-align: center; margin-top: 24pt; }
/* DISCLAIMER */
.disclaimer { font-size: 8pt; color: #64748B; font-style: italic; text-align: center; margin-top: 24pt; }
/* ACCENT LINE */
hr { border: none; border-top: 3px solid #CC785C; margin: 12pt 0; }
hr.thin { border-top: 1px solid #E3DACC; }
/* BANNED: green, red, amber, blue, navy, gradients, shadows, opacity, box-shadow */
Element-to-Color Quick Reference
| Report Element | Color | Hex | Constant |
|---|---|---|---|
| Table header fill | Light brand | #F6EAE6 | AGENTMAN_100 |
| Table header text | Terracotta | #CC785C | AGENTMAN_500 |
| Table borders | Warm separator | #E3DACC | AGENTMAN_150 |
| Section titles | Charcoal | #292322 | CHARCOAL_900 |
| Section underline | Terracotta | #CC785C | AGENTMAN_500 |
| Body text | Charcoal | #141413 | CHARCOAL_950 |
| Secondary text | Slate | #475569 | SLATE_600 |
| Footer/meta | Slate muted | #64748B | SLATE_500 |
| Alternate row bg | Cream | #FAF9F5 | AGENTMAN_50 |
| Highlight row bg | Warm cream | #F0EEE6 | AGENTMAN_75 |
| Stat numbers | Terracotta | #CC785C | AGENTMAN_500 |
| Positive emphasis | Terracotta bold | #CC785C | AGENTMAN_500 |
| Negative emphasis | Deep terracotta bold | #A65945 | AGENTMAN_600 |
| Badges (all tiers) | Light brand bg + deep text | #F6EAE6 bg, #703B2D text | AGENTMAN_100 + AGENTMAN_800 |
| Cover accent | Terracotta | #8B4A38 | AGENTMAN_700 |
Typography
Font: Helvetica (built-in to reportlab) or Calibri (if registered). Use Calibri when available, Helvetica as fallback.
| Element | Size | Weight | Alignment |
|---|---|---|---|
| Report title | 24pt (leading 30) | Bold | Left |
| Subtitle | 13pt (leading 17) | Regular | Left |
| Section heading (h2) | 16–18pt | Bold | Left |
| Subsection heading (h3) | 13–14pt | Bold | Left |
| Body text | 10.5–11pt (leading 15) | Regular | Left |
| Stat number | 28pt (leading 34) | Bold | Center (in table cell) |
| Stat label | 10pt (leading 13) | Regular | Center (in table cell) |
| Table header | 11pt | Bold (600) | Center |
| Table body | 10pt | Regular | Center (data), Left (labels) |
| Footer | 8pt | Italic | Center |
NEVER center body paragraphs. Left-align all body text.
Banned Colors
| Color | Hex | Why Banned |
|---|---|---|
| Green (success) | #10B981 | Traffic-light aesthetic (except ✓ symbol) |
| Amber (warning) | #F59E0B | Dashboard alert feel |
| Red (error) | #EF4444 | Alarm aesthetic (except ✗ symbol) |
| Blue (info) | #3B82F6 | Generic corporate, dilutes brand |
| Navy | #1B3A5C | Corporate default, not Agentman |
| Cyan | #0891B2 | Too techy |
| Pure black | #000000 | Use #141413 (CHARCOAL_950) instead |
| Generic grays | #333, #666, #999, #ccc | Use brand palette equivalents |
| Any gradient | N/A | Solid fills only |
The ONE exception: #10B981 (green ✓) and #EF4444 (red ✗) for comparison table symbols ONLY — never for text, backgrounds, borders, or any other element.
Overlap Prevention Rules
Text overlap is the #1 layout defect. Follow these rules strictly:
- Stat blocks MUST use
agentman_stat_row()table layout. Never stackSTYLE_STAT_NUMBERandSTYLE_STAT_LABELparagraphs sequentially — the 28pt number will overlap the 10pt label. Always wrap stat pairs in a Table. - Every ParagraphStyle MUST have explicit
leading(line height). Theleadingvalue must be at least 1.2x thefontSize. Without explicit leading, reportlab defaults can cause text to overlap. - Title + subtitle need
STYLE_TITLE(leading=30) thenSTYLE_SUBTITLE(leading=17). Never use the same style for both — the large title font will collide with text below if spacing is insufficient. - Use
Spacer(1, 12)between unrelated content blocks to guarantee breathing room. - Side-by-side content MUST use a Table, not separate centered Paragraphs. Centered paragraphs stack vertically and overlap.
- Set
spaceBeforeandspaceAfteron every style. Never rely on implicit spacing.
Pre-Generation Checklist
Before executing ANY PDF generation code, verify:
- No text overlap — Stat blocks use
agentman_stat_row()table layout, title/subtitle have separate styles with adequate leading - Color constants defined — ALL Agentman constants are in the script
- No library defaults — No
colors.black,colors.grey,getSampleStyleSheet(), default table styles - Document author set —
"Agentman Equity Research Assistant"in metadata - Page 1 author line — "Author: Agentman Equity Research Assistant" displayed before data
- Title starts with "Agentman —"
- Table headers are LIGHT —
AGENTMAN_100(#F6EAE6) fill +AGENTMAN_500(#CC785C) text - No dark table headers — No navy, dark blue, dark brown, charcoal, or any saturated color as header fill
- No banned colors — No green, red, amber, blue, navy, or generic grays
- Warm borders —
AGENTMAN_150(#E3DACC), not black/gray - Alternating rows — White /
AGENTMAN_50(#FAF9F5) cream - Terracotta budget — Max 3
AGENTMAN_500elements per section - Footer on every page — "Agentman Equity Research Assistant · {date} · Data: FMP"
- No gradients, shadows, or opacity — Solid fills only
- Charts use brand palette — Never library defaults
- Visual weight feels 70/20/10 — Mostly charcoal, some cream, rare terracotta