Agentman DOCX Style Guide (JavaScript / docx-js)
When to Use
Load this skill before writing any DOCX generation code using docx-js. This skill provides the complete Agentman brand implementation for Word document 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 DOCX generation code, you MUST:
- Define ALL color constants below as the first lines of your script
- Set document metadata — creator, title, company
- Display author on page 1 immediately after the report title
- NEVER let Word default styles bleed through — override every heading, body text, and table style
- NEVER use default heading colors — Word defaults are blue/black, not Agentman charcoal
- NEVER use ShadingType.SOLID — always ShadingType.CLEAR to prevent black backgrounds
- Always use WidthType.DXA — never WidthType.PERCENTAGE (breaks in Google Docs)
- Inter for headings and document title. Calibri for everything else.
Font Strategy
Two fonts. One rule: Inter for headings, Calibri for everything else.
| Font | Elements | Why |
|---|---|---|
| Inter | Document title, H1, H2, H3 | Geometric, modern — stronger visual anchor; matches brand's editorial posture |
| Calibri | Body text, tables, footer, captions, stat blocks | Universally available, renders consistently without embedding |
Inter must be installed on the generating machine or embedded in the output. Download from https://fonts.google.com/specimen/Inter (free, OFL license).
Never substitute Inter headings with Calibri, Arial, or Helvetica. If Inter is unavailable, log a warning and fall back to Calibri — do not silently use a different font.
Visual Weight Distribution
- 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
Color Constants
const fs = require("fs");
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
Header, Footer, AlignmentType, HeadingLevel, BorderStyle, WidthType,
ShadingType, VerticalAlign, PageNumber, PageBreak } = require("docx");
// Font constants
const FONT_HEADING = "Inter"; // H1, H2, H3, document title
const FONT_BODY = "Calibri"; // All other text
// Primary Brand (Terracotta) — use sparingly, max 3 per section
const AGENTMAN_800 = "703B2D"; // Badge text
const AGENTMAN_700 = "8B4A38"; // Cover accent
const AGENTMAN_600 = "A65945"; // Negative emphasis
const AGENTMAN_500 = "CC785C"; // PRIMARY ACCENT — stat numbers, table header text
const AGENTMAN_400 = "D97757"; // Highlight/caution
const AGENTMAN_200 = "E6A890"; // Badge borders
const AGENTMAN_150 = "E3DACC"; // Table borders, separators
const AGENTMAN_100 = "F6EAE6"; // Table header fill
const AGENTMAN_75 = "F0EEE6"; // Highlight row bg, callout blocks
const AGENTMAN_50 = "FAF9F5"; // Alternate row bg (cream)
// Text Colors (Charcoal) — 70% of visual weight
const CHARCOAL_950 = "141413"; // PRIMARY BODY TEXT
const CHARCOAL_900 = "292322"; // Section titles, cover title
const CHARCOAL_800 = "3D3735"; // Secondary text
// Neutral (Slate)
const SLATE_600 = "475569"; // Metric labels
const SLATE_500 = "64748B"; // Footer, muted captions
Standard Borders
const BRAND_BORDER = { style: BorderStyle.SINGLE, size: 1, color: AGENTMAN_150 };
const BRAND_BORDERS = { top: BRAND_BORDER, bottom: BRAND_BORDER, left: BRAND_BORDER, right: BRAND_BORDER };
const NO_BORDER = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
const NO_BORDERS = { top: NO_BORDER, bottom: NO_BORDER, left: NO_BORDER, right: NO_BORDER };
const TERRACOTTA_BOTTOM_BORDER = { bottom: { style: BorderStyle.SINGLE, size: 6, color: AGENTMAN_500, space: 1 } };
Document Setup
const PAGE_WIDTH = 12240; // 8.5in DXA (CRITICAL: docx-js defaults to A4)
const PAGE_HEIGHT = 15840; // 11in DXA
const PAGE_MARGIN = 1080; // 0.75in
const CONTENT_WIDTH = PAGE_WIDTH - (PAGE_MARGIN * 2); // 10080
function createAgentmanDoc(reportTitle, sections) {
return new Document({
creator: "Agentman Equity Research Assistant",
title: `Agentman — ${reportTitle}`,
company: "Agentman (Chain of Agents, Inc.)",
styles: {
default: { document: { run: { font: FONT_BODY, size: 22, color: CHARCOAL_950 } } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 48, bold: true, font: FONT_HEADING, color: CHARCOAL_900 },
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 36, bold: true, font: FONT_HEADING, color: CHARCOAL_900 },
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: FONT_HEADING, color: CHARCOAL_900 },
paragraph: { spacing: { before: 120, after: 120 }, outlineLevel: 2 } },
],
},
sections,
});
}
const SECTION_PROPS = {
page: { size: { width: PAGE_WIDTH, height: PAGE_HEIGHT },
margin: { top: PAGE_MARGIN, bottom: PAGE_MARGIN, left: PAGE_MARGIN, right: PAGE_MARGIN } },
};
Cover Page
function buildCoverPage(reportTitle, dateStr, subtitle) {
const today = dateStr || new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
const children = [
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [
new TextRun({ text: `Agentman — ${reportTitle}`, font: FONT_HEADING, bold: true, size: 48, color: CHARCOAL_900 })
]}),
];
if (subtitle) children.push(new Paragraph({ spacing: { before: 40, after: 120 }, children: [
new TextRun({ text: subtitle, font: FONT_BODY, size: 26, color: SLATE_600 })
]}));
children.push(
new Paragraph({ spacing: { after: 60 }, children: [
new TextRun({ text: "Author: Agentman Equity Research Assistant", font: FONT_BODY, size: 22, color: SLATE_600 })
]}),
new Paragraph({ spacing: { after: 200 }, children: [
new TextRun({ text: `${today} | Data Source: Financial Modeling Prep`, font: FONT_BODY, size: 22, color: SLATE_600 })
]}),
terracottaRule()
);
return children;
}
Helper Functions
function terracottaRule(thickness = 18) {
return new Paragraph({ spacing: { before: 60, after: 120 }, border: { bottom: { style: BorderStyle.SINGLE, size: thickness, color: AGENTMAN_500, space: 1 } }, children: [] });
}
function sectionHeading(text, level = HeadingLevel.HEADING_2) {
return new Paragraph({ heading: level, border: TERRACOTTA_BOTTOM_BORDER, spacing: { before: 240, after: 120 }, children: [
new TextRun({ text, font: FONT_HEADING, bold: true, size: level === HeadingLevel.HEADING_2 ? 36 : 28, color: CHARCOAL_900 })
]});
}
function bodyText(text) { return new Paragraph({ spacing: { after: 120 }, children: [new TextRun({ text, font: FONT_BODY, size: 22, color: CHARCOAL_950 })] }); }
function secondaryText(text) { return new Paragraph({ spacing: { after: 80 }, children: [new TextRun({ text, font: FONT_BODY, size: 20, color: SLATE_600 })] }); }
Table Styling
function agentmanTable(headers, data, colWidths) {
const numCols = headers.length;
if (!colWidths) { const w = Math.floor(CONTENT_WIDTH / numCols); colWidths = Array(numCols).fill(w); colWidths[numCols - 1] = CONTENT_WIDTH - w * (numCols - 1); }
const cm = { top: 80, bottom: 80, left: 120, right: 120 };
const headerRow = new TableRow({ children: headers.map((h, i) => new TableCell({
borders: BRAND_BORDERS, width: { size: colWidths[i], type: WidthType.DXA },
shading: { fill: AGENTMAN_100, type: ShadingType.CLEAR }, margins: cm, verticalAlign: VerticalAlign.CENTER,
children: [new Paragraph({ alignment: AlignmentType.CENTER, children: [
new TextRun({ text: h, font: FONT_BODY, bold: true, size: 22, color: AGENTMAN_500 })
]})]
}))});
const bodyRows = data.map((row, ri) => new TableRow({ children: row.map((cell, ci) => new TableCell({
borders: BRAND_BORDERS, width: { size: colWidths[ci], type: WidthType.DXA },
shading: { fill: ri % 2 === 0 ? "FFFFFF" : AGENTMAN_50, type: ShadingType.CLEAR },
margins: cm, verticalAlign: VerticalAlign.CENTER,
children: [new Paragraph({ alignment: ci === 0 ? AlignmentType.LEFT : AlignmentType.CENTER, children: [
new TextRun({ text: String(cell), font: FONT_BODY, bold: ci === 0, size: 20, color: CHARCOAL_950 })
]})]
}))}));
return new Table({ width: { size: CONTENT_WIDTH, type: WidthType.DXA }, columnWidths: colWidths, rows: [headerRow, ...bodyRows] });
}
Stat Block Layout (MANDATORY — Prevents Overlap)
CRITICAL: Never use stacked paragraphs for stat blocks. Always use a Table.
function statRow(statPairs) {
const n = statPairs.length;
const colW = Math.floor(CONTENT_WIDTH / n);
const colWidths = Array(n).fill(colW);
colWidths[n - 1] = CONTENT_WIDTH - colW * (n - 1);
const numberRow = new TableRow({ children: statPairs.map(([value], i) => new TableCell({
borders: NO_BORDERS, width: { size: colWidths[i], type: WidthType.DXA }, verticalAlign: VerticalAlign.CENTER,
children: [new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 100, after: 20 }, children: [
new TextRun({ text: value, font: FONT_BODY, bold: true, size: 56, color: AGENTMAN_500 })
]})]
}))});
const labelRow = new TableRow({ children: statPairs.map(([, label], i) => new TableCell({
borders: NO_BORDERS, width: { size: colWidths[i], type: WidthType.DXA }, verticalAlign: VerticalAlign.CENTER,
children: [new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 0, after: 100 }, children: [
new TextRun({ text: label, font: FONT_BODY, size: 20, color: SLATE_600 })
]})]
}))});
return new Table({ width: { size: CONTENT_WIDTH, type: WidthType.DXA }, columnWidths: colWidths, rows: [numberRow, labelRow] });
}
Callout Block
function calloutBlock(heading, body) {
return new Table({ width: { size: CONTENT_WIDTH, type: WidthType.DXA }, columnWidths: [CONTENT_WIDTH],
rows: [new TableRow({ children: [new TableCell({
borders: BRAND_BORDERS, width: { size: CONTENT_WIDTH, type: WidthType.DXA },
shading: { fill: AGENTMAN_75, type: ShadingType.CLEAR }, margins: { top: 120, bottom: 120, left: 200, right: 200 },
children: [
new Paragraph({ children: [new TextRun({ text: heading, font: FONT_BODY, bold: true, size: 24, color: CHARCOAL_950 })] }),
new Paragraph({ spacing: { before: 60 }, children: [new TextRun({ text: body, font: FONT_BODY, size: 20, color: SLATE_600 })] }),
]
})]})],
});
}
Footer and Disclaimer
function agentmanFooter(dateStr) {
const today = dateStr || new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
return new Footer({ children: [new Paragraph({
border: { top: { style: BorderStyle.SINGLE, size: 3, color: AGENTMAN_500, space: 4 } },
alignment: AlignmentType.CENTER, children: [
new TextRun({ text: `Agentman Equity Research Assistant · ${today} · Data: FMP `, font: FONT_BODY, size: 16, color: SLATE_500 }),
new TextRun({ children: ["Page ", PageNumber.CURRENT], font: FONT_BODY, size: 16, color: SLATE_500 }),
]
})]});
}
function disclaimer() {
return [ new Paragraph({ children: [] }), terracottaRule(6),
new Paragraph({ alignment: AlignmentType.CENTER, children: [new TextRun({
text: "This Agentman research report is for informational purposes only and does not constitute personalized investment advice. Prepared by Agentman Equity Research Assistant.",
font: FONT_BODY, size: 16, italics: true, color: SLATE_500 })]})
];
}
Key Rules
- Stat blocks MUST use statRow() table layout — never stacked paragraphs
- Always ShadingType.CLEAR — never ShadingType.SOLID
- Always WidthType.DXA — never WidthType.PERCENTAGE
- Set both columnWidths on Table AND width on each TableCell
- Page size US Letter: width 12240, height 15840
- Title must start with "Agentman —"
- Terracotta budget: max 3 per section
- Inter (
FONT_HEADING) on all headings (H1, H2, H3) and document title - Calibri (
FONT_BODY) on ALL other TextRuns — body, tables, footer, stat blocks, captions - Visual weight: 70% charcoal, 20% neutrals, 10% terracotta
- Banned colors: green, amber, red, blue, navy, pure black, Word default blue, generic grays, gradients