# Design System The aesthetic layer. Read this before touching any script. This file answers "what should it look like and why." --- ## The one rule Every design decision must be **rooted in the document's content and purpose**. Dark teal + cream is not "professional". Serif + beige is not "elegant". A color chosen because it fits the content will always outperform a color chosen because it seems safe. --- ## Palette logic `palette.py` takes a short content description and outputs `tokens.json`. Here is the reasoning it applies: ### Mood → base palette | Content signal | Mood | Background | Accent | Text | |---|---|---|---|---| | Research, science, analysis | Authoritative | `#0F1F2E` deep ink | `#00B4A6` teal | `#F0EDE6` warm white | | Business, strategy, finance | Confident | `#1C1C2B` near-black | `#E8A020` amber | `#F5F2EC` cream | | Creative, portfolio, design | Expressive | `#1A0A2E` deep violet | `#FF6B6B` coral | `#FAF5FF` lavender white | | Education, academic paper | Scholarly | `#FAFAF7` warm white | `#2C4A7C` navy | `#1A1A2E` dark | | Healthcare, wellness | Calm | `#F5F9F8` pale mint | `#2D8B72` forest | `#1E3830` deep green | | Resume / personal | Clean | `#FFFFFF` white | pick from content | `#111111` near-black | | General / unknown | Neutral | `#F8F6F1` warm off-white | `#3D3D3D` dark gray | `#1A1A1A` black | | Formal publications, annual reports | Magazine | `#F2F0EC` warm linen | `#1C3557` deep navy | `#0D1A2B` near-black | | Premium/dark reports, tech reviews | Darkroom | `#151C27` deep navy | `#4A6FA5` steel blue | `#F0EDE6` warm white | | Technical docs, developer reports | Terminal | `#0D1117` near-black | `#39D353` neon green | `#E6EDF3` cool white | | Portfolios, creative, photography | Poster | `#FFFFFF` white | `#0A0A0A` near-black | `#0A0A0A` near-black | ### Accent selection rules - **One accent color only.** Using two accents splits visual energy. - Accent appears on: cover geometric elements, section rules, callout left borders, table header background, page header rule. Nowhere else. - Accent must contrast with the cover background by at least 4.5:1 (WCAG AA). - Do not default to blue. Blue is the most overused accent in AI-generated documents. ### Color pairing anti-patterns (never use these) | ❌ Avoid | Why | |---|---| | Purple gradient on white | The default AI aesthetic — immediately signals "generated" | | Navy + gold | Overused corporate cliché | | All-black background | Prints badly, feels aggressive | | More than 3 colors in the system | Visual noise | | Accent on body text | Destroys readability | --- ## Typography system ### Font pairing logic Two typefaces maximum. Always. | Role | Criteria | Good choices (system-safe) | |---|---|---| | Display (cover title, H1) | Distinctive, strong contrast, high weight | Times New Roman, Georgia (serif) | | Text (body, captions, UI) | Highly readable at 10–11pt | Helvetica, Arial (sans) | Cover fonts are loaded live via `@import url(...)` in the cover HTML — Playwright fetches them at render time, no local caching. Body pages always use system fonts (Times-Bold / Helvetica) via ReportLab — consistent and offline-safe. Pairs by mood (cover HTML only — body always uses system fonts): - Authoritative: `Playfair Display` / `IBM Plex Sans` - Confident: `Syne` / `Nunito Sans` - Expressive: `Fraunces` / `Inter` - Scholarly: `EB Garamond` / `Source Sans 3` - Clean: `DM Serif Display` / `DM Sans` - Restrained: `Cormorant Garamond` / `Jost` - Bold: `Barlow Condensed` / `Barlow` - Dynamic: `Montserrat` / `Montserrat` - Classical: `Cormorant` / `Crimson Pro` - Editorial: `Bebas Neue` / `Libre Franklin` - Body fallback (always): `Times-Bold` / `Helvetica` (ReportLab system fonts) ### Type scale All sizes in points. This scale is used by `palette.py` to populate `tokens.json`. | Token | Size | Leading | Usage | |---|---|---|---| | `display` | 54pt | 1.0 | Cover title | | `h1` | 22pt | 1.3 | Section headings | | `h2` | 15pt | 1.4 | Subsection headings | | `h3` | 11.5pt | 1.5 | Sub-subsection | | `body` | 10.5pt | 1.6 | Main prose | | `caption` | 8.5pt | 1.4 | Figure/table captions | | `meta` | 8pt | 1.3 | Header/footer text | ### Spacing system Margins and rhythm are what separate "looks designed" from "looks printed". | Token | Value | Notes | |---|---|---| | `margin_outer` | 2.8cm | Left/right page margin | | `margin_top` | 2.8cm | Top page margin | | `margin_bottom` | 2.5cm | Bottom page margin | | `section_gap` | 26pt | Space before H1 | | `para_gap` | 8pt | Space after paragraph | | `line_gap` | 17pt | Leading for body text | Never use ReportLab's default margins (too tight). Always set explicitly. --- ## Cover design The cover is the most important page. It determines whether a reader trusts the document. ### Thirteen cover patterns `cover.py` selects one based on `tokens.json["cover_pattern"]`. **1. `fullbleed`** — used for: `report`, `general` - Deep background fills 100% of page - Title: large, left-aligned, upper 60% of page - Accent: thin horizontal rule + top-right corner strip - Dot-grid background texture (subtle, 8–10% opacity) - Footer band: author + date metadata - Fonts: Playfair Display / IBM Plex Sans **2. `split`** — used for: `proposal` - Left 42% panel: solid cover color, title + author - Right 58%: off-white, dot-grid decoration - Hard vertical dividing line in accent color - No gradients — pure flat geometry - Fonts: Syne / Nunito Sans **3. `typographic`** — used for: `resume`, `academic` - White/off-white background - Name or title as oversized display type (60–80pt), left-aligned - First word in accent color, remainder in dark - Thin rule below title block - Fonts: DM Serif Display / DM Sans (resume) · EB Garamond / Source Sans 3 (academic) **4. `atmospheric`** — used for: `portfolio` - Near-black background - Soft radial glow in accent color (upper-right quadrant) - Title centered-left, 2 lines max - Short rule in accent below title - Dot-grid texture at low opacity - Fonts: Fraunces / Inter **5. `minimal`** — used for: `minimal` - Near-white background, 8px left accent bar is the only color - Title in very large, light-weight display type (300 weight) - Hairline rule, author + date as single muted line - Nothing else — the bar does all the visual work - Fonts: Cormorant Garamond / Jost **6. `stripe`** — used for: `stripe` - Page cut into three horizontal bands: accent / dark / light - Top band: category label; middle: oversized title in white; bottom: metadata - Hard edges, no gradients, no textures — newspaper / brand poster aesthetic - Fonts: Barlow Condensed / Barlow **7. `diagonal`** — used for: `diagonal` - SVG polygon cuts page diagonally: dark upper-left, light lower-right - Accent-colored edge line traces the diagonal cut - Title on dark area, metadata on light area - Fonts: Montserrat / Montserrat **8. `frame`** — used for: `frame` - White/cream background with an inset rectangular border (1.2px, 28px from edges) - Accent strips inside top + bottom of frame; small accent corner squares - Title centered in the frame space, centered alignment, classical weight - Formal, timeless — annual reports, legal documents, academic papers - Fonts: Cormorant / Crimson Pro **9. `editorial`** — used for: `editorial` - Ghost first-letter of title fills upper-right at 5% opacity — visual texture - 5px accent top bar; full-width uppercase title in condensed weight - Title all-caps, very large (80px), flush-left - Footer rule + author/date metadata - Fonts: Bebas Neue / Libre Franklin **10. `magazine`** — used for: `magazine` - Warm cream/linen background; fully centered, vertical stack layout - Org/company name in small spaced caps + 2px accent rule beneath (top anchor) - Large bold serif title (52px) centered; short accent rule under title - Italic subtitle; optional `cover_image` URL renders as centered hero thumbnail - Optional `abstract` field: justified text block with bold "Abstract:" label - Author name in accent color (large, bold); date beneath - Fonts: Playfair Display / EB Garamond **11. `darkroom`** — used for: `darkroom` - Same centered stack layout as `magazine` but deep navy background, white text - Org name + rules in semi-transparent white; accent rules desaturated - Hero image (if provided) gets `grayscale(20%) brightness(0.9)` filter - Fonts: Playfair Display / EB Garamond **12. `terminal`** — used for: `terminal` - Near-black background; neon green accent; Space Mono monospace throughout - Grid overlay: faint horizontal + vertical lines at 48px intervals (7% opacity) - Status label top-left: green dot + `SYSTEM_REPORT // ` - Title inside a bracket frame (border-left + border-top + pseudo-element corner) - Subtitle prefixed with `>` in accent color - Abstract text left; author block right; status bar at bottom (UTF-8 / Ln 1) - Fonts: Space Mono / Space Mono **13. `poster`** — used for: `poster` - White background; thick 52px left sidebar in accent (typically near-black) - Title: 96px, 900-weight, all-caps, condensed — the dominant visual element - Subtitle in typewriter font below title; thin 2px rule as separator - Author + meta in Courier Prime monospace beneath rule - Optional `cover_image` rendered as 260×340 grayscale thumbnail, right-aligned - Accent square icon block (lower-right) with white horizontal lines - Fonts: Barlow Condensed / Courier Prime ### Optional token: `cover_image` Patterns `magazine`, `darkroom`, and `poster` accept an optional `cover_image` token containing an absolute URL or `file://` path to an image. The image renders via `` — Playwright fetches it at render time. If omitted, the image area is simply skipped (layout adjusts gracefully). ### Cover CSS requirements (critical for Playwright rendering) These three rules must appear in every cover HTML file or the output will have white borders / incorrect dimensions: ```css body { margin: 0; padding: 0; } html, body { width: 794px; height: 1123px; overflow: hidden; } ``` No `@page` rules needed — Playwright handles page size via the `pdf()` call. Do NOT use CSS `background-image` for textures — use inline SVG or ``. Always use `position: absolute` + `z-index` for layered elements. ### What always kills a cover - Centered title on white background with a thin horizontal line underneath - Gradient from one color to another (reads as PowerPoint, not print design) - Drop shadows on text - More than one accent color - Emoji or icon fonts (fail silently on headless Chromium) --- ## Inner page rules ### What "restraint" means in practice Every design decision should remove something, not add something. The page is done when there is nothing left to remove. - Accent color appears on section rules only — not on headings, not on bullets - No card components (bordered boxes with colored headers) - No rounded corners on anything except callout boxes (4px max) - No shadows anywhere - Tables: header row in accent, alternating row tint, no grid lines except outer box - Callout boxes: left border in accent (4px), very light tint background, no icon ### Page header / footer Header: document title (left, 7.5pt, muted) + accent rule (1.5pt, full width below) Footer: author name (left, 7.5pt, muted) + page number (right, 7.5pt, muted) + light rule above --- ## Quality bar A PDF passes if a designer would not be embarrassed to hand it to a client. Concretely: - Cover has a clear visual identity that is not "generic AI output" - Body text is readable at arm's length without squinting - Every page looks like it belongs to the same document - No element bleeds off the edge or overlaps another - Page numbers are present and correct - The accent color appears fewer than 8 times per page on average --- ## Block type reference All body blocks use the same token system — colors and fonts come from `tokens.json`, never hardcoded. | Block | Rendering | Design notes | |---|---|---| | `h1` | 22pt heading + full-width accent rule below | KeepTogether with rule — heading never orphaned | | `h2` | 15pt heading, dark text | No rule, no accent — visual hierarchy through size only | | `h3` | 11.5pt bold, dark text | **No accent color** — accent on body headings violates the one-accent-location rule | | `body` | 10.5pt justified, 17pt leading | Supports `` `` `` markup | | `bullet` | Body size with `•` prefix, 14pt indent | Use for unordered lists | | `numbered` | Body size with `N.` prefix, hanging indent | Counter auto-resets on any non-numbered block — no manual numbering needed | | `callout` | Accent left-border (4px) + light tint background | Max one callout per section — overuse kills impact | | `table` | Accent header row, alternating row tint, outer box only | Supports `col_widths` (fractions, e.g. `[0.3, 0.5, 0.2]`) for custom column widths | | `image` | Scaled to column width, preserving aspect ratio | Use `path` or `src`; always provide a `caption` | | `figure` | Same as image, but caption auto-prefixed "Figure N:" | Figure counter increments across all `figure`, `chart`, `flowchart` blocks | | `code` | Courier 8.5pt, accent left-border, light tint background | Supports optional `language` label (rendered above block) | | `math` | Formula centered, optional right-aligned equation label | LaTeX syntax; matplotlib mathtext renderer | | `chart` | Bar / line / pie chart rendered via matplotlib | Color palette derived from document accent; figure auto-numbered | | `flowchart` | Process diagram with labeled arrows | Supports 4 node shapes; back-edges drawn as curved arcs | | `bibliography` | Numbered reference list with hanging indent | Heading rendered as h2 + accent rule; items as `[N] text` | | `divider` | Accent-colored 1.2pt rule with padding | Use sparingly — only for major thematic breaks | | `caption` | 8.5pt muted text, centered | Appears below images/tables via field or explicit block | | `pagebreak` | Force page break | — | | `spacer` | Vertical whitespace | `pt` field (default 12) | ### Math formula guidance **Input syntax:** standard LaTeX math notation — `\frac{}{}`, `\int`, `\sum`, `\alpha`, `^`, `_`, etc. **Rendering engine:** matplotlib mathtext — pure Python, no LaTeX compiler, no browser required. | Syntax example | Rendered as | |---|---| | `E = mc^2` | Inline expression | | `\frac{\sqrt{\pi}}{2}` | Fraction | | `\int_0^\infty e^{-x^2} dx` | Integral | | `\sum_{i=1}^{n} x_i` | Summation | | `\alpha + \beta = \gamma` | Greek letters | **Limitations:** matplotlib mathtext covers most common expressions but not advanced LaTeX environments (`align`, `cases`, `matrix`). Split complex multi-line proofs into multiple `math` blocks. **Fallback:** if matplotlib is not installed, renders as `expression` in code style. Run `make.sh fix` to install. **Equation labels:** `"label": "(1)"` — rendered right-aligned beside the formula. ### Chart guidance **Rendered entirely in Python** — no external chart services, image files, or internet required. | chart_type | Use case | Required fields | |---|---|---| | `bar` | Comparing discrete categories | `labels`, `datasets` | | `line` | Trends over time or ordered categories | `labels`, `datasets` | | `pie` | Part-to-whole composition | `labels`, `datasets[0].values` | - Colors are derived from the document accent for visual consistency — do not set custom colors. - Multi-series: add multiple objects to `datasets`, each with a `label` and `values` array. - Figure auto-numbering: set `"figure": true` (default) or `"figure": false` to suppress. ### Flowchart guidance **Node shapes:** | shape | Use for | |---|---| | `rect` (default) | Process step | | `diamond` | Decision / condition | | `oval` or `terminal` | Start / End | | `parallelogram` | Input / Output | - Nodes are placed in input order (top to bottom). This controls the layout. - Forward edges draw straight arrows; back-edges (to earlier nodes) draw curved arcs. - Keep labels short (3–5 words max) — the diagram is A4-column-width at 78% scale. - Figure auto-numbering applies same as chart. ### Bibliography guidance - `id` field is the reference label — use numbers ("1", "2") or alphanumeric ("Smith23"). - Text should be in a consistent citation style (APA, Chicago, etc.) — the renderer does not enforce style. - The `title` field defaults to "References". Set `"title": ""` to suppress the heading. - A `bibliography` block always starts with a new section heading + accent rule. ### Image / figure guidance - Preferred formats: PNG, JPEG - Scaled down if wider than the text column; never scaled up - `figure` blocks auto-number; `image` blocks do not — use `figure` for numbered figures - If the file does not exist at render time, a `[Image not found]` placeholder is substituted ### Code block guidance - Preserves whitespace exactly — do not indent code in the JSON value - Optional `language` field renders a small language label above the block (e.g., `"language": "python"`) - No syntax highlighting (by design) — consistent with restraint principle - Keep lines under ~90 characters for A4 column width