# Executive Report Design System

> Produces multi-page, print-ready HTML executive reports in a fixed editorial design language that does not look machine-generated. Assembles pages from a closed catalogue of named components (hero statement, KPI row, bar list, inline SVG trend, compare strip, status grid, flow strip, numbered cards, prose, risk callout, pull quote, data table) against a frozen stylesheet and a validated JSON data document, so every report is visually identical in language whatever the subject. Output is a single self-contained file with no CDN, no server, no web fonts and no charting library, that prints to A4 landscape one sheet per page and runs from SharePoint, OneDrive or an email attachment. Figures animate up to their value on screen and snap to final on paper. Use when the user says "build an executive report", "quarterly business review", "QBR deck as a document", "board report", "exec summary", "multi-page HTML report", "printable report", "PDF-ready report", "management report", "monthly report pack", "make this loo

- Skill: `itspecialist111/executive-report-design-system` (Agent Skill, multi-file: 21 files)
- Install (CLI): `npx skillmds@latest add itspecialist111/executive-report-design-system`
- Raw SKILL.md: https://api.skillmd.com/api/skills/itspecialist111/executive-report-design-system/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: ITSpecialist111 (https://skillmd.com/u/itspecialist111)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/itspecialist111/executive-report-design-system

---


# Executive Report Design System

Assemble multi-page HTML executive reports from a closed component catalogue and a
frozen stylesheet, so that every report looks like a designer built the template and
an analyst filled it in — never like a language model improvised a layout.

## When to Use

- User wants a recurring report (QBR, board pack, monthly review) that must look the
  same every time
- User has data — a CSV, a spreadsheet, a set of figures — and needs it presented, not
  just tabulated
- User needs a document that prints properly to A4 and survives being emailed or
  stored in SharePoint
- User explicitly complains that AI-generated documents look generic
- User wants a single self-contained file with no external dependencies

## When NOT to Use

- Slide deck → use the `pptx` skill
- Word document or letter → use the `docx` skill
- A live dashboard that re-reads changing data → build a SharePoint Framework web part
- One quick chart with no narrative → answer inline instead
- A report where the user wants to choose their own colours and layout — this skill is
  deliberately not configurable

---

## Quick Start

**User**: "Build a Q3 executive summary from the attached CSV. Audience is the exec
team; the headline is that we beat plan but delivery slipped."

**Steps**:
1. Ask the seven interview questions below. Do not guess any of them.
2. Read the data. Decide which components tell the story, using the decision table in
   `references/component-catalogue.md`.
3. Build the JSON data document. Validate it:
   `python scripts/validate_report.py report.json`
4. Render: copy `assets/design-system.css` into a `<style>` block and
   `assets/motion.js` into an inline `<script>`, then assemble pages from
   `assets/component-snippets.md`.
5. Lint the output: `python scripts/check_output.py report.html`
6. Fix anything the linter flags. Do not hand over a report that fails the lint.

---

## The One Rule

> **You are not designing. You are assembling.**

The design already exists, in `assets/reference-report.html`. Your job is to pick the
right blocks from a fixed catalogue, fill them with the user's data, and emit the file.
You do not get a vote on colour, type, spacing or layout.

**If you find yourself writing a new CSS rule, you have gone wrong.** Stop and pick a
different component.

---

## Why This Skill Exists — The Anti-Generic Problem

Left alone, a language model produces a recognisable house style: a centred hero, three
or four identical cards with emoji, a purple-to-blue gradient, rounded corners
everywhere, `Inter` at 16px, generous uniform padding, a *Key Insights* heading,
bullet lists of three, and a footer saying *Generated by AI*.

Everybody has seen it. It reads as a template, and it quietly tells the reader that
nobody spent any time on this. For an executive audience that is an own goal — the
document undermines its own content.

This design system is opinionated in the specific ways a generic generator never is:

| The generic instinct | What this system does instead |
|---|---|
| Gradient hero, saturated brand colour | Warm paper `#F7F3EC`, one restrained red, used sparingly |
| Big bold headline, 700 weight | Very large, very **light** (300) headline — confidence, not shouting |
| Colour everywhere | Colour carries meaning only. Red means "look here" or "this is at risk" |
| Every card identical | A hierarchy: hero sentence, then KPIs, then evidence, then the ask |
| Emoji and icon chips | No emoji, no icon fonts, anywhere, ever |
| Rounded 12–16px corners, big shadows | 4px radius, a 1px rule, a shadow you can barely see |
| Centred text | Left-aligned, ragged right, like a printed report |
| "Key Insights", "Overview", "Summary" | Headings that state a finding |
| Three bullets of filler | A sentence that states the finding, then the number that proves it |
| Full-width scrolling page | Fixed A4 pages. It is a document, not a web page |

The full ban list, with rationale, is in `references/anti-generic-rules.md`.
`scripts/check_output.py` enforces the mechanical parts of it.

**The test:** a reader should assume a designer made the template and an analyst filled
it in. Nothing about the output should suggest a machine chose the layout.

---

## How It Works

### The three layers

Only one of them changes per report. This is what makes the output deterministic.

```
  STYLE          CSS custom properties. Four palette classes.        never changes
                 Set one on .page; every colour resolves through it.

  COMPONENTS     A closed catalogue of named blocks. The skill        never changes
                 picks which and in what order. It never invents one.

  DATA           A validated JSON document. Every value, every        changes per report
                 label, every block choice lives here.
```

Same JSON in, same HTML out. If a report needs regenerating six months later, the JSON
reproduces it exactly.

### The authoring pipeline

```
  START      Beautiful HTML example        assets/reference-report.html      once
  EXTRACT    Design read into tokens       assets/design-system.css          once
  CODIFY     Catalogue + rules             SKILL.md + references/            once
  ─────────────────────────────────────────────────────────────────────────────
  EVERY TIME User sends prompt + data      "build a Q3 QBR from this CSV"
  OUTPUT     Validated JSON document       scripts/validate_report.py
  RESULT     Self-contained HTML           scripts/check_output.py
```

The first three steps are already done — they are this package. In use, the whole
interaction is a two-line prompt and a data file.

### Page model

Every page is a fixed A4 landscape box containing exactly three children:

```html
<section class="page palette-warm-paper" id="p3">
  <div class="page-head">
    <span class="wordmark">Contoso</span>
    <span class="page-meta">Q3 Executive Summary &middot; Exec Team</span>
  </div>

  <div class="page-body stack gap-lg">
    <!-- three to five components -->
  </div>

  <div class="page-foot">
    <span>Contoso &middot; Q3 executive summary</span>
    <span>Page <span class="pageno"></span> of 9</span>
  </div>
</section>
```

**Content must fit.** Nothing scrolls, nothing spills. If a page overflows, move a block
to the next page — never shrink the type to make it fit.

Running heads and feet are **real elements repeated on every page**. This is deliberate
and non-negotiable: `@page` margin boxes are still unsupported in Chromium, and
`position: fixed` does not repeat per page outside Firefox. In-DOM is the only approach
that prints identically in Edge, Chrome, Firefox and Safari. See
`references/pagination-rules.md` before changing anything about printing.

### Motion

Figures count up with an ease-out quint curve, bars sweep out, the trend line draws
itself, and cards rise in with a short stagger, triggered per page as it scrolls into
view. You do not write this and you do not configure it — just do not break it:

- The engine reads the **final text already in the HTML**, replays the arrival, then
  writes the identical string back. Write real values into the markup: `$1.9M`, never a
  placeholder or a zero.
- Prefixes and suffixes survive: `$1.9M` counts through `$1.3M`, `4/8` counts through
  `2/8`, `+18.4%` keeps its sign and decimal.
- Bar widths are authored inline (`style="width:61%"`). The engine parks them at zero
  and hands the value back.
- `prefers-reduced-motion` is honoured, and everything snaps to final on `beforeprint`,
  so paper never catches a half-counted number.

---

## Hard Constraints

These come from the report having to survive being emailed, stored in SharePoint, and
opened on a machine with no internet.

| Constraint | Why |
|---|---|
| One file, everything inline | Survives email, SharePoint, a USB stick |
| No CDN, no `<script src>`, no `<link href>`, no `@import`, no `@font-face` | Locked-down tenants block egress; SharePoint page CSP restricts script origins |
| System fonts only | No network call, and Segoe UI on Windows is the face the design was drawn in |
| Charts are inline SVG or CSS | SheetJS and Chart.js are unavailable or blocked in the target environments |
| Inline `<script>` for animation only, never for data | The report must be complete and correct with scripting off |
| A4 landscape, one `.page` per sheet | It is a document, not a web page |

`scripts/check_output.py` verifies all of these mechanically.

---

## Procedure

### 1. Interview

Ask all seven. Do not guess, and do not proceed on assumptions:

1. What is the report, and who reads it?
2. What period does it cover?
3. What is the single most important finding? *(this becomes the hero sentence)*
4. What are the three or four headline numbers?
5. What decision, if any, do you need from the reader?
6. Organisation name for the wordmark, and any classification label?
7. Which palette? *(default to `palette-warm-paper`)*

### 2. Build the JSON

Against `references/report.schema.json`. Content, component choice and palette token
only — no geometry, no hex colours, no pixel values. Those live in the frozen
stylesheet, and keeping them out of the data is what prevents drift.

### 3. Validate

```bash
python scripts/validate_report.py report.json
```

If it does not validate, fix the JSON. Do not render around a validation failure.

### 4. Render

Copy the frozen blocks unaltered. Assemble pages from `assets/component-snippets.md`.
Write real final values into the markup.

### 5. Lint

```bash
python scripts/check_output.py report.html
```

Fix everything it flags before handing over.

---

## Choosing Components — The Decision Table

Do not deliberate. Match the shape of the data:

| The data is… | Use |
|---|---|
| One number that matters most | `.hero` sentence, figure in `.accent` |
| Three or four headline metrics | `.kpi-row` |
| A metric plus its change | `.kpi-card` with `.kpi-delta` |
| Ranked categories | `.bar-list` plus `.legend` |
| A time series | `.trend` inside a `.panel` |
| This period against last | `.compare-strip` |
| Named items with a state | `.status-grid` |
| Rows the reader will interrogate | `.data-table` in a `.panel` |
| A sequence of steps | `.flow-strip` |
| Parallel concepts | `.numbered-cards` |
| Things needing a decision | `.highlights` |
| One urgent thing | `.risk-callout` |
| Explanation, caveats, method | `.prose.cols-2` |
| A memorable quotation | `.pull-quote` |

**Emphasis is rationed.** At most one `.is-emphasis` and one `.risk-callout` per page.
If everything is emphasised, nothing is.

Full class inventory: `references/component-catalogue.md`.

---

## Page Order

| Page | Content | Required? |
|---|---|---|
| 1 | Cover — title, rule, meta row | Yes |
| 2 | Contents — printed, with page numbers | Yes for 6+ pages |
| 3 | Executive summary — hero sentence, KPIs, one chart | Yes |
| 4..n | Evidence — performance, delivery, whatever the story needs | Yes |
| n+1 | Method and assumptions — how the figures were derived | Strongly recommended |
| n+2 | Section divider — full bleed, `palette-deep-ink` | Only for long reports |
| n+3.. | Appendix | Optional |

Give a page three to five components. Two looks thin; seven looks like a dashboard.
Use `grow` on the last block so a page never ends in a void.

---

## Writing the Words

The prose is half of why this does not look machine-made.

- **Headings state findings, not topics.** Not *"Delivery Status"* but *"Eight active
  initiatives, four fully on track"*.
- **The hero sentence is one sentence** with two or three figures in `.accent`. It
  should survive being read aloud.
- **Captions earn their place.** "10 deals closed" is useful. "Total revenue" under a
  card labelled Total Revenue is not.
- **Red captions mean risk.** `.kpi-note.is-alert` for *"2 at risk · 1 blocked"*. Never
  for good news.
- **British English.** Optimise, prioritise, organisation.
- **No hedging.** *"Legal review has run eleven weeks"*, not *"There may be some
  potential delays in the legal review process"*.
- Numbers in prose match the cards exactly. Never restate a figure differently.

---

## Files in This Package

| File | Purpose |
|---|---|
| `assets/design-system.css` | The frozen stylesheet. Copy verbatim. |
| `assets/motion.js` | The frozen motion engine. Copy verbatim. |
| `assets/reference-report.html` | The visual target — nine finished pages, every component, all four palettes. |
| `assets/component-snippets.md` | Copy-paste HTML for every component. |
| `references/component-catalogue.md` | Exhaustive class inventory and the decision table. |
| `references/anti-generic-rules.md` | The ban list, with rationale. |
| `references/pagination-rules.md` | Print CSS that actually works, and the myths that do not. |
| `references/report.schema.json` | The data contract. |
| `references/example-report.json` | A worked instance. |
| `scripts/validate_report.py` | Validates a data document. Standard library only. |
| `scripts/check_output.py` | Lints generated HTML against the hard constraints. |

---

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Content spills past the page foot | Too much on one page | Move a block to the next page. Never reduce the font size. |
| A blank page appears when printing | `break-after: page` on the last page | Ensure `.page:last-of-type { break-after: auto }` is present — it is in the frozen CSS |
| Backgrounds print white | Browser is stripping backgrounds | `print-color-adjust: exact` must be present — it is in the frozen CSS |
| Numbers do not animate | Values written as `0` or a placeholder | Write the real final value into the markup |
| Numbers animate to the wrong value | Something rewrote the text after load | Nothing may modify a figure after render; remove it |
| Page numbers all read the same | CSS counter reset in the wrong place | `body { counter-reset: pageno }` and `.page { counter-increment: pageno }` |
| Bars are invisible | `.bar-fill` rendering as inline | It must be `display: block` — it is in the frozen CSS |
| Report looks generic despite the system | Components used decoratively | Re-read `references/anti-generic-rules.md`; check emphasis is rationed |
| Fonts look heavier than the reference | Machine has no Segoe UI Light | Expected fallback. Arial has no 300 weight; the layout still holds |

---

## Self-Check Before Handing Over

- [ ] `python scripts/check_output.py report.html` passes
- [ ] Frozen CSS and JS copied unaltered; no CSS rule added, no class invented
- [ ] Every page fits — nothing overflows past the running foot
- [ ] Every page has head, body and foot; the page total is written in
- [ ] Cover and executive summary present; contents page numbers correct
- [ ] Every figure appears in the markup as its final text
- [ ] Red used only for meaningful figures and genuine risk
- [ ] Charts with more than one style have a legend
- [ ] Headings state findings, not topics
- [ ] Opens correctly with JavaScript disabled

