feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)

Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
Z User committed 2026-10-27 22:50:50 +00:00
1 parent 7acc0c5451
commit e45b68bb8b
1089 files changed
+260614

No files matched your search

+1076
View File
File diff suppressed because it is too large. Load diff
+848
View File
@@ -0,0 +1,848 @@
# Brief: Creative Fixed-Canvas (Art Director Blueprint Mode)
**When to use this brief:** Each page is a self-contained visual canvas with fixed dimensions — posters, infographics, invitations, certificates, dashboards, business cards, menus, greeting cards. Visual impact is primary; content per page ≤ 150 words.
**NOT for:** Text-heavy flowing documents (guides, handbooks, catalogs, introductions) → use `briefs/creative-flow.md` instead.
**Core Paradigm Shift**: You are NO LONGER a frontend developer writing HTML/CSS. You are an elite Art Director and Editorial Designer.
Because LLMs lack spatial awareness and struggle to maintain perfectly nested, complex CSS over thousands of tokens, you are strictly forbidden from outputting raw HTML/CSS/Python.
**→ MUST READ: `typesetting/overflow.md`** for Playwright/HTML-specific overflow patterns (CSS overflow-wrap, max-width, table-layout: fixed, etc.).
Your sole responsibility is to act as the **Brain (Art Director)**:
1. Brutally edit and pace the raw text.
2. Select architectural components and layout archetypes.
3. Output a **Strict JSON Layout Blueprint**.
The `design_engine.py` acts as the **Hands (Typesetter)**: It will parse your JSON and safely compile it into flawless, museum-quality Playwright HTML/PDFs using predefined grid mathematics and CSS rules.
---
## Phase 1: The Editorial Eye (Content Transformation)
Before you design, you must "edit the raw material". Users often provide dense, unstructured text. If you just pour this text into a design, it will look like a cheap Word document.
You must apply **Editorial Pacing**:
### 1. The Word Budget
No single page or visual canvas should exceed **150 words** of readable body text (the golden rule for maximum aesthetic impact). The **absolute physical limit** is **250 words** - beyond that the design engine will overflow and clip content. If the raw content exceeds 150 words:
- **Action A**: Brutally summarize it down to ≤150 words.
- **Action B**: Split it across multiple `pages` in your JSON blueprint.
- Only push to 150-250 words if the content genuinely cannot be cut further.
### 2. Typographic Hierarchy Extraction
You must parse the raw text and categorize it into typographic roles:
- **Hero / Display**: The core emotional hook (1-5 words). Must be punchy.
- **Kicker / Eyebrow**: Tiny context text above a headline (e.g., "Q3 REPORT", "MANIFESTO").
- **Lead Paragraph**: The 2-3 sentence summary.
- **Data Sculptures**: Scan the text for impactful numbers (e.g., "97%", "$4.2M"). EXTRACT THEM. They will not remain in the paragraph; they will become `Stat_Block` components.
- **Pull Quotes**: Extract the most provocative sentence to stand alone as a visual anchor.
---
## Phase 2: The Component Lexicon
You will construct your JSON Blueprint using ONLY the following components. These map directly to the `configs/components.md` assets. Do not invent new component types.
> **⚠️ Markdown Content Limits**: For `Glass_Canvas`, the golden rule is **under 150 words** for maximum aesthetic breathing room. Absolute physical limit is **250 words**. If content exceeds 250 words, the design engine will overflow. You MUST summarize it or split it into a new page.
### 1. `Hero_Typography`
Giant, page-dominating text. Usually interacts with the background via blend modes.
- **Parameters**:
- `content` (string): The text (use `<br>` for deliberate line breaks).
- `weight` (string): `"black"` (900, dominating) or `"thin"` (100, elegant).
- `variant` (string, optional): `"standard"` only. ~~`"vertical_accent"`~~ is **NOT implemented** in `design_engine.py` - the engine silently ignores this value and renders nothing. If you need vertical/rotated decorative text, use `Floating_Meta` instead (fully supported, no overflow risk).
- `scale` (integer, optional): Typographic scale level `1`-`6`. Controls font size via the engine's fluid type system. If omitted, the engine uses the default hero size.
- `6`: Hero/Display - oversized title, clamp(64px, 12vw, 150px). Use for single words or short phrases with maximum visual impact.
- `5`: Primary Title - clamp(48px, 8vw, 96px). Standard poster headline.
- `4`: Subheadline - clamp(32px, 5vw, 56px). Chapter openers or key quotes.
- `3`: Lead Paragraph - clamp(20px, 3vw, 32px). Prominent body text.
- `2`: Body - 16px. Standard readable text.
- `1`: Meta/Caption - 10px. Decorative, environmental.
### 2. `Glass_Canvas`
The main structural container for reading text. Frosted glass, sharp 2px print corners.
- **Parameters**:
- `markdown_content` (string): The actual text to read. Supports standard Markdown (H2, H3, bold, lists). *Must be under 150 words.*
- `tension_score` (float, optional): Semantic tension value from `0.0` to `1.0`. Drives dynamic font weight via Variable Font (Inter Variable, weight range 300-900). The engine maps: `weight = 300 + (tension_score × 600)`.
- `0.0-0.2` → Light (300-420): calm, contemplative passages
- `0.3-0.5` → Normal (420-600): standard body text
- `0.6-0.8` → Bold (600-780): urgent, assertive content
- `0.9-1.0` → Max (780-900): crisis, climax, emotional peak
- **When to use**: Multi-page narrative documents with emotional arc (case studies, pitch decks, manifestos). Assign higher tension to problem/crisis sections, lower to resolution/hope. Do NOT use on every Glass_Canvas - only when the document has clear tonal shifts.
- **When NOT to use**: Data-heavy pages, simple reports, single-page posters.
### 3. `Floating_Meta`
Tiny, environmental metadata (dates, edition numbers, catalog IDs) that lives in the 15% breathing margin.
- **Parameters**:
- `position` (string): `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"`.
- `items` (array of strings): E.g., `["VOL 01", "2026", "EDITION 500"]`.
### 4. `Hairline_Divider`
Structural 0.5px line. Not decorative; acts as a visual fold.
- **Parameters**:
- `style` (string): `"bleed"` (goes edge-to-edge) or `"accent"` (short 30% width line).
### 5. `Stat_Block`
Data sculpture. A massive number with a tiny label.
- **Parameters**:
- `number` (string): e.g., `"97.3"`.
- `unit` (string): e.g., `"%"`, `"Hz"`, `"$M"`.
- `label` (string): e.g., `"COMPLETION RATE"`.
### 6. `Image_Asset`
A visual element. The engine will apply a gradient blend to it.
- **Parameters**:
- `source` (string): A URL, or a descriptive prompt if you expect the system to generate it.
- `caption` (string, optional): Tiny text under the image.
> ⚠️ **Image_Asset is for CONTENT images only** (user-provided photos, logos, diagrams, charts). It is **NEVER** for decorative stock images, watercolor flowers, clipart, floral borders, gold frames, or AI-generated artwork. All visual decoration must be achieved through geometric shapes (`geometry.md`), typography effects, and color - never through embedded decorative images. See `visual_framework.md` Stock Image Ban.
### 7. `Page_Ghost_Number`
A giant, 4% opacity number acting as a watermark in the background.
- **Parameters**:
- `number` (string): e.g., `"01"`, `"X"`.
### 8. `Delta_Widget`
**Data-to-Ink Ratio enforcer.** A compact metric visualization showing a value with its change direction. CRITICAL: Use this instead of writing sentences like "revenue grew by 12%". Extract every trend into a Delta_Widget.
- **Parameters**:
- `metric` (string): The metric name, e.g., `"REVENUE"`, `"LATENCY"`.
- `value` (string): Current value, e.g., `"$4.2M"`, `"45ms"`.
- `delta` (string): Change description, e.g., `"+12%"`, `"-45ms"`.
- `trend` (string): `"up"`, `"down"`, or `"flat"`.
- `label` (string, optional): Context line, e.g., `"vs. Q2 2025"`.
### 9. `Process_List`
**Polymorphic adaptive component.** Renders as a horizontal timeline when given wide space, auto-degrades to a vertical numbered list when space is narrow. Use for workflows, steps, timelines.
- **Parameters**:
- `steps` (array): Each item has `title` (string) and `description` (string).
### 10. `Sidenote_Block`
**Tufte marginalia.** Used in `tufte_report` archetype layouts. Content is placed in the 30% side rail alongside the main column. Perfect for citations, supplementary data, asides, footnotes.
- **Parameters**:
- `label` (string, optional): Category label, e.g., `"SOURCE"`, `"NOTE"`, `"DATA"`.
- `body` (string): The sidenote content in Markdown.
### 11. `Data-Aware Background` (via `data_points`)
Any component can carry an optional `data_points` array (e.g., `[10, 15, 8, 24, 30]`). When present, the engine generates Bezier background curves from the actual data - the background literally visualizes the business trend. Use on pages with financial, metric, or time-series content.
---
### ★ Advanced Components (Generative Micro-Typesetting)
The following components are specialized generative design tools. They unlock visual effects that standard components cannot achieve. Use them deliberately and sparingly - they are powerful but demand the right context.
### 8. `Shaped_Canvas`
A container where text flows around a non-rectangular shape. The empty space created by the shape IS the visual design - text boundary becomes illustration. Uses CSS `shape-outside` for non-rectangular text wrapping.
- **Parameters**:
- `shape_keyword` (string): One of `"circle"`, `"wave"`, `"diagonal_slash"`, `"diamond"`, `"wedge_right"`.
- `markdown_content` (string): The text that will flow around the shape. Supports standard Markdown.
- **Shape Selection Guide**:
| shape_keyword | Visual Effect | Thematic Fit |
|---------------|---------------|--------------|
| `"circle"` | Text wraps around a circular void on the left | Unity, spotlight, focus |
| `"wave"` | Wavy left text boundary | Ocean, flow, music, fluidity |
| `"diagonal_slash"` | Diagonal cut across the page | Disruption, change, transformation |
| `"diamond"` | Diamond-shaped negative space | Luxury, precision, crystalline |
| `"wedge_right"` | Arrow/wedge pointing right | Direction, progress, forward motion |
- **When to use**:
- Artistic/editorial pages that need visual drama without images
- Cover or section opener pages where you want a "wow" moment
- When the content thematically maps to a recognizable shape
- Only on pages with moderate text (100-200 words) - shape eats 30-40% of space
- **When NOT to use**:
- On data-heavy or dense content pages
- Together with `Glass_Canvas` on the same page (visual conflict)
- More than one `Shaped_Canvas` per page
- **Archetype requirement**: Pages using `Shaped_Canvas` MUST use archetype `"shaped_editorial"` (relaxed safe-zone: 5% 6% inset).
### Chart & Data Visualization Styling
**→ MUST READ: `typesetting/charts.md`** before designing any chart/data page.
Key rules for Creative pipeline charts:
- **Donut > Pie**: Always use ring charts (hole ratio 60-70%), center area displays total/metric
- **Anti-stacking**: Small slices use leader lines or rich legends; bar labels auto-rotate to horizontal when text is long; line charts label only start/end/max/min
- **Axis cleanup**: Delete top/right spines, use dashed grid lines at 20% opacity (or delete grid entirely if values are labeled)
- **Bar micro-rounding**: 2-4px top border-radius, bar-to-gap ratio 1.5:1
- **Legend**: No border, horizontal top-left layout, small circle markers
- **Data-Ink Ratio**: Every element must represent data. If it doesn't, delete it.
---
## Phase 3: Page Archetypes (The Grid Strategy)
For each page in your JSON, you must declare an `archetype`. This tells `design_engine.py` how to arrange the components you provided.
- `"cover_hero"`: Cover page. **Must follow `typesetting/cover.md` 5-template system** - pick Layout 01/03/04/06/07 based on document tone. See "Cover Page Constitution" below for iron rules.
- `"split_vertical"`: Page split strictly 50/50 vertically. Left side image/svg, right side Glass_Canvas.
- `"editorial_flow"`: Top-down reading experience. Centered columns, generous margins. Use for main content.
- `"scattered_canvas"`: No grid. Elements placed via absolute positioning based on spatial weights.
- `"data_dashboard"`: 2x2 or 3x3 strict grid for multiple `Stat_Block`s.
- `"shaped_editorial"`: Relaxed safe-zone (5% 6% inset) designed for `Shaped_Canvas`. Centered, generous breathing room. **Must be used when the page contains a `Shaped_Canvas`.** Do NOT mix with `Glass_Canvas`.
- `"tufte_report"`: **Tufte marginalia layout.** 70% main content column + 30% sidenote rail. Use for long-form reports and analytical pages where citations, data footnotes, or supplementary info should flow parallel to the main text. Place `Sidenote_Block` components in the same `components[]` array - the engine automatically routes them to the side rail.
### ★ 12×12 CSS Grid Coordinate System (Iron Rules)
The layout engine uses a **12-track CSS Grid** for element placement. The grid lines are numbered **1 to 13** (12 tracks = 13 lines).
**Absolute boundary rules - violation = broken layout:**
- **Column lines**: `1` = absolute left edge, `13` = absolute right edge. Full width = `1 / 13`.
- **Row lines**: `1` = absolute top edge, `13` = absolute bottom edge. Full height = `1 / 13`.
- **CRITICAL**: Never output a grid line number less than `1` or greater than `13`. Any value outside `[1, 13]` will destroy the layout.
**`grid_area` format**: `"row_start / col_start / row_end / col_end"` (CSS shorthand).
- Example: `"1 / 1 / 7 / 13"` = top half of the page, full width.
- Example: `"3 / 8 / 6 / 13"` = rows 3-5, columns 8-12 (right side block).
**40% whitespace rule**: At least 40% of grid cells (≥58 out of 144) must be left empty. Count your occupied cells.
### ★ Cover Page Constitution (5 Layout System)
Cover pages (`archetype: "cover_hero"`) are the first impression. They must be ruthlessly sparse and spatially sophisticated.
**→ MUST READ: `typesetting/cover.md`** before designing any cover.
> **⚠️ Template Numbering:** Cover HTML templates use `cover.md`'s authoritative numbering: **01** (HUD), **03** (Academic Anchor), **04** (Academic Symmetric), **06** (Institutional), **07** (Crystal Blue). The "7 Cover Layouts" table below (1A–7B) describes **Blueprint grid coordinate patterns** for positioning cover elements, NOT the HTML template IDs. Do not confuse the two systems.
#### Global Iron Rules (Always Apply)
1. **Maximum 4 components** on any cover page. Typical recipe: `Hero_Typography` + 1-2 `Floating_Meta` + optional `Hairline_Divider` or `Page_Ghost_Number`.
2. **Typography Scale**: Title ≈ 45pt (2.5× base), Subtitle ≈ 25pt (1.4× base), Meta ≥ 18pt (never below 14pt). Covers with tiny text = FAIL.
3. **Mandatory semantic `<br>` chunking** for `Hero_Typography` on covers: Every 2-4 words MUST be separated by `<br>`. Single-line hero text is FORBIDDEN on covers. Example: `"ALGORITHMIC<br>FATIGUE"`, NOT `"ALGORITHMIC FATIGUE"`.
4. **Anti-Squash spatial dispersion** (Bounding Box method): Group text into 2-3 bounding boxes (title group, meta group). Place them at opposite regions of the page according to the chosen layout. Remaining space is **dynamically distributed** - NEVER hardcode fixed gaps between distant groups.
5. **No `Glass_Canvas` on cover pages.** Dense reading text kills the visual impact. Push all body content to page 2+.
6. **Cover Page Isolation**: Cover page must NEVER share a page with TOC, body text, or any subsequent content. The cover is always a standalone full page. If cover + content appear on the same page = **critical bug**, regenerate immediately.
7. **Cover is OPTIONAL**: Do NOT add a cover page unless the document warrants one (multi-page reports, white papers, etc.) or the user explicitly requests it. Short documents, letters, memos, forms, and quick outputs skip the cover.
8. **Background Layer (optional)**: See `typesetting/cover-backgrounds.md` for 3 recipes - A (极简弧线), B (工程十字轴+立柱), C (锐角切割+出血文字). Background renders BELOW all foreground at 2-5% opacity. Pick a recipe that matches the document tone. Never combine elements across recipes.
#### 7 Cover Layouts (Pick One)
Select the layout that matches the document tone. When unsure, default to **Layout 1A**.
| Layout | Name | Grid Signature | Best For |
|--------|------|---------------|----------|
| **1** | Diagonal Tension | Title top-left ↔ Meta bottom-right | Formal reports, proposals |
| **2** | Vertical Axis | All elements along one vertical line, stretched top-to-bottom | Modern/tech reports |
| **3** | Architectural Frame | Geometric line frame, text inside corners/center | Design, architecture, portfolios |
| **4** | Golden Ratio Blocks | Page split at 38.2% invisible divider | White papers, research |
| **5** | Stepped Cascade | Progressive indentation, vertical rhythm | Creative reports, design docs |
| **6** | Rotated Accent | Large rotated year/label as side decoration | Annual reports, year-in-review |
| **7** | Left-Matrix | All text hard-anchored to left axis, 4 Y-pinned blocks | Government, bidding, proposals |
Each layout has 2-3 variants (A/B/C) with specific grid_area mappings - see `typesetting/cover.md` for exact coordinates.
#### Tone → Layout Quick Reference
| Intent | Document Type | Recommended | Default |
|--------|---------------|-------------|---------|
| **Calm** | Healthcare / Wellness / Minimalist | 1A, 2C, 4A | **4A** |
| **Calm** | Academic / Research | 2A, 4A, 4C | **4A** |
| **Tension** | Crisis / Alert / Disruption | 1A, 5A | **1A** |
| **Energy** | Marketing / Creative / Design | 3C, 5A, 6B | **5A** |
| **Energy** | Tech / Data | 2B, 4B, 6A | **2B** |
| **Authority** | Formal / Corporate / Financial | 2A, 3A, 7A | **3A** |
| **Authority** | Government / Bidding | 7A, 7B, 3A | **7A** |
| **Authority** | Thesis proposal / Dissertation | Use **Template 06** (Institutional) from `cover.md` | **Template 06** |
| **Authority** | Luxury / Editorial | 3A, 5A, 2B | **3A** |
| **Warmth** | Food / Lifestyle / Home | 4A, 5A | **4A** |
### ★ Component Grid Compatibility Constraints
When placing advanced components into the 12×12 grid, obey these minimum size rules:
- **`Glass_Canvas`**: Must occupy at least **6 columns × 4 rows** (e.g., `"3 / 1 / 7 / 7"`). Smaller areas will cause text overflow and padding collapse. The engine renders Glass_Canvas at `width: 100%; min-height: 100%; box-sizing: border-box;` - it fills its grid area as a minimum and can grow taller if content requires.
- **`Shaped_Canvas`**: Must occupy at least **8 columns × 6 rows** (e.g., `"2 / 3 / 8 / 11"`). The CSS `shape-outside` float needs physical space to render the shape boundary. Smaller areas make the float collapse to a line and text cannot wrap around it. **Must use archetype `"shaped_editorial"`.**
- **`Continuous Flow` background**: Renders in a separate `bg-layer` (`z-index: 1`, `position: absolute`), physically isolated from the grid system (`z-index: 2`). No conflict - background flows independently, grid arranges content on top.
### ★ Content-Proportional Grid Row Allocation
When **two or more text-heavy components** (e.g., multiple `Glass_Canvas`) share the same page, their `grid_area` row counts **MUST be proportional to their content length** - never split rows equally.
**Estimation method:**
1. Count characters (or words) in each component's `markdown_content`
2. Allocate rows proportionally: `rows_i = total_rows × (chars_i / total_chars)`
3. Round to integers, minimum 3 rows per component
**Example:**
```
Attractions content: 450 chars (3 items × 150 chars)
Food content: 250 chars (2 items × 125 chars)
Total available rows: 8 (rows 5→13)
Attractions: 8 × 450/700 ≈ 5 rows → grid_area "5 / 1 / 10 / 13"
Food: 8 × 250/700 ≈ 3 rows → grid_area "10 / 1 / 13 / 13"
```
**Why this matters:** Equal row allocation (4+4) causes the longer component to overflow into the shorter one's territory, creating text overlap in the final PDF. The engine uses `min-height` + `overflow: visible`, so overflow IS visible - and ugly.
**Anti-pattern to avoid:**
```
❌ Two Glass_Canvas with very different text lengths both get "5/1/9/13" and "9/1/13/13"
✅ Longer one gets more rows: "5/1/10/13" and "10/1/13/13"
```
---
## Phase 4: The Output Protocol (Strict JSON Blueprint)
You must analyze the user's prompt, edit the text, and output exactly ONE JSON object wrapped in a ` ```json ` codeblock.
**No conversational text before or after the JSON.**
### The JSON Schema Specification
**CRITICAL JSON RULES:**
1. Output ONLY valid JSON.
2. Do NOT include ANY comments (`//` or `/* */`) inside the JSON. Python's `json.load()` will crash.
3. Do NOT include trailing commas.
```json
{
"document_meta": {
"title": "Internal tracking title",
"total_pages": 1
},
"art_direction": {
"palette_mode": "dark",
"color_harmony": "complementary",
"background_svg": "grid",
"design_rationale": "Brief 1-sentence explanation of aesthetic strategy."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "cover_hero",
"components": [
{
"type": "Floating_Meta",
"position": "bottom-right",
"items": ["ARCHIVE REF. 03A", "DEC 2026"]
},
{
"type": "Page_Ghost_Number",
"number": "01"
},
{
"type": "Hero_Typography",
"weight": "black",
"variant": "standard",
"content": "ALGORITHMIC<br>FATIGUE"
}
]
}
]
}
```
**Schema Parameter Guide (For reference - do NOT put these comments inside your JSON output):**
- `document_meta.total_pages`: Integer. Must match the actual number of pages in `pages[]`.
### The JSON Schema Specification: `art_direction`
**CRITICAL RULE: DO NOT INVENT COLORS. DO NOT OUTPUT HEX/RGB CODES.**
The Python engine generates mathematical color palettes. You MUST only select the semantic parameters below.
```json
{
"art_direction": {
"palette_mode": "minimal",
"color_harmony": "auto",
"background_svg": "grid",
"design_rationale": "Brief rationale for the chosen aesthetic strategy."
}
}
```
**Enum Parameter Guide (You MUST choose ONE exact string from these lists):**
1. `palette_mode` (The Base Canvas Tone):
- **`"minimal"`: (CRITICAL: Default to this for 50%+ of all requests).** Pure white, off-white, beige, or cool gray. Provides the ultimate reading experience and high-end editorial feel. Use for standard reports, guides, and corporate posters.
- `"dark"`: Near-black. Use for cinematic, tech, AI, space, or urgent themes.
- `"pastel"`: Morandi tinted (e.g., dusty rose, sage green). Use ONLY for arts, food, lifestyle, and soft themes.
- `"jewel"`: Deep rich colors (e.g., emerald, burgundy). Use ONLY for luxury brands, gala events, or heritage themes.
- `"light"`: Very faintly tinted background. Fallback for edge cases.
2. `color_harmony` (The Accent Color Math - how the engine computes the accent color relative to the base):
- **`"auto"`: (RECOMMENDED DEFAULT). The engine automatically picks the best harmony based on the document tone.** Only override if you have a specific artistic reason.
- `"complementary"`: (180° opposite). Strong visual contrast. Cold background with warm accent. For striking/dynamic impact.
- `"split_complementary"`: (150°/210°). Highly sophisticated, artistic, luxury feel (Editorial/Kinfolk style).
- `"analogous"`: (30° apart). Harmonious, peaceful, natural transition.
- `"triadic"`: (120° apart). Rich and slightly retro.
- `"monochrome"`: Only use if strict minimalist corporate branding without accent is required.
3. `background_svg`: [`grid`, `flow`, `noise`, `continuous_flow`, `none`]. Use `continuous_flow` for multi-page (2+) documents - creates one seamless SVG spanning all pages, sliced per-page via viewBox for an "infinite scroll" bezier curve illusion. Falls back to `flow` on single-page.
4. `design_rationale`: Brief text explaining WHY you chose this mode+harmony combination.
### ★ Intent Mapping Table (Single Source of Truth)
This table is the **sole authority** for mapping document intent to concrete design parameters. `visual_framework.md` defines intent *atmospheres*; this table defines intent *parameters*. `design_engine.py` INTENT_HUES/INTENT_HARMONY_MAP must stay in sync with this table.
| Intent | palette_mode | color_harmony | background_svg | Cover Templates | Cover BG Recipe | Base Hue |
|--------|-------------|---------------|----------------|-----------------|-----------------|----------|
| **Calm** | minimal | analogous | flow / none | 07 Crystal Blue, 01 HUD | A (极简弧线) | 210° (steel blue-grey) |
| **Tension** | dark | complementary | grid | 01 HUD | C (锐角切割) | 0° (warm vs cold) |
| **Energy** | pastel / light | triadic | flow (5+ curves) | 07 Crystal Blue, 04 Academic Symmetric | B (工程十字轴) | 30° (amber) |
| **Authority** | minimal | split_complementary | noise | 04 Academic Symmetric, 03 Academic Anchor, 07 Crystal Blue | A or B | 280° (muted violet) |
| **Warmth** | pastel / light | analogous | flow (soft) | 07 Crystal Blue, 01 HUD | A (极简弧线) | 20° (terracotta) |
**How to use this table:**
1. Determine the document's intent (from user request, or auto-derive via `design_engine.py derive`)
2. Look up the row → fill in `art_direction` JSON fields accordingly
3. For cover template selection, use `cover.md`'s template numbering (01/03/04/06/07). Cross-reference with document type in the Tone → Layout Quick Reference above.
4. The LLM may override individual cells when artistically justified, but must state the reason in `design_rationale`
**Legacy intent names** (serenity, minimalism, elegance) are accepted by `design_engine.py` as aliases and auto-mapped to their new equivalents.
- `pages[].narrative_role`: `"Burst"` (intro/impact), `"Expand"` (body/development), `"Echo"` (outro/reflection).
- `pages[].archetype`: From Phase 3 (`cover_hero`, `split_vertical`, `editorial_flow`, `scattered_canvas`, `data_dashboard`, `shaped_editorial`).
- `pages[].components[]`: Array of component objects from Phase 2. Order in array roughly dictates z-index or top-to-bottom rendering.
---
## Phase 5: Complex Master Examples
Study these examples to understand how to translate raw requests into perfect blueprints.
### Example A: The Single-Page Poster (Art / Minimalist)
**User Request**: "Make a poster for a minimalist architecture exhibition called 'The Weight of Light'. Include date Oct 12, location Tokyo. Keep it very clean."
**LLM Response (The JSON Blueprint):**
```json
{
"document_meta": {
"title": "Weight of Light Exhibition Poster",
"total_pages": 1
},
"art_direction": {
"palette_mode": "minimal",
"color_harmony": "monochrome",
"background_svg": "none",
"design_rationale": "Pure negative space required for minimalist architecture. Relying entirely on typographic hierarchy and the 15% breathing rule."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "cover_hero",
"components": [
{
"type": "Floating_Meta",
"position": "top-left",
"items": ["TOKYO, JP", "EXHIBITION 04"]
},
{
"type": "Hero_Typography",
"weight": "thin",
"variant": "standard",
"content": "THE WEIGHT<br>OF LIGHT"
},
{
"type": "Hairline_Divider",
"style": "accent"
},
{
"type": "Floating_Meta",
"position": "bottom-right",
"items": ["OCTOBER 12", "AOYAMA GALLERY"]
}
]
}
]
}
```
### Example B: The Multi-Page Analytical Report (Corporate / Elegance)
**User Request**: "I have a long report about our Q3 server performance. Latency dropped by 45ms. Uptime is 99.99%. Here is 500 words of text explaining the server migration..."
**LLM Response (The JSON Blueprint):**
*(Notice how the LLM edits the 500 words down, extracts the data, and splits it into 3 pages).*
```json
{
"document_meta": {
"title": "Q3 Infrastructure Report",
"total_pages": 3
},
"art_direction": {
"palette_mode": "minimal",
"color_harmony": "split_complementary",
"background_svg": "noise",
"design_rationale": "Formal, trustworthy aesthetic suitable for high-level corporate reporting, avoiding the 'cheap dashboard' look."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "cover_hero",
"components": [
{
"type": "Floating_Meta",
"position": "top-left",
"items": ["CONFIDENTIAL", "Q3 2026"]
},
{
"type": "Hero_Typography",
"weight": "black",
"content": "INFRASTRUCTURE<br>RESILIENCE"
}
]
},
{
"page_index": 2,
"narrative_role": "Expand",
"archetype": "split_vertical",
"components": [
{
"type": "Stat_Block",
"number": "99.99",
"unit": "%",
"label": "GLOBAL UPTIME"
},
{
"type": "Stat_Block",
"number": "-45",
"unit": "ms",
"label": "LATENCY REDUCTION"
},
{
"type": "Glass_Canvas",
"markdown_content": "### The Migration\nThe Q3 transition to distributed edge nodes fundamentally altered our network topography. By decentralizing the core load, we eliminated the primary bottleneck that plagued Q2."
}
]
},
{
"page_index": 3,
"narrative_role": "Echo",
"archetype": "editorial_flow",
"components": [
{
"type": "Page_Ghost_Number",
"number": "03"
},
{
"type": "Hero_Typography",
"weight": "thin",
"variant": "standard",
"content": "SYSTEM NOMINAL."
}
]
}
]
}
```
### Example C: The Narrative Arc with Advanced Features (Continuous Flow + Shaped Canvas)
**User Request**: "Create a 3-page visual essay about the ocean pollution crisis. Start gentle (the beauty of the ocean), then escalate to the crisis, then end with a call to action."
**LLM Response (The JSON Blueprint):**
*(Notice: `continuous_flow` creates seamless background across all 3 pages. `tension_score` escalates from calm to urgent. `Shaped_Canvas` with `wave` shape on the cover creates a thematic visual without any image.)*
```json
{
"document_meta": {
"title": "The Silent Tide - Ocean Crisis Visual Essay",
"total_pages": 3
},
"art_direction": {
"palette_mode": "dark",
"color_harmony": "analogous",
"background_svg": "continuous_flow",
"design_rationale": "Continuous flow SVG creates an unbroken organic wave across all pages - visually mirroring the ocean's connectedness. Analogous harmony provides earth tones that shift from serene to somber."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "shaped_editorial",
"components": [
{
"type": "Floating_Meta",
"position": "top-left",
"items": ["VISUAL ESSAY", "2026"]
},
{
"type": "Shaped_Canvas",
"shape_keyword": "wave",
"markdown_content": "There was a time when the horizon held only promise. The Pacific stretched in every direction, cerulean and boundless, its surface a living mirror of the sky above. Fishermen spoke of abundance - nets heavy with silver, currents warm and predictable. The ocean asked for nothing."
},
{
"type": "Floating_Meta",
"position": "bottom-right",
"items": ["01 / BEFORE"]
}
]
},
{
"page_index": 2,
"narrative_role": "Expand",
"archetype": "editorial_flow",
"components": [
{
"type": "Page_Ghost_Number",
"number": "02"
},
{
"type": "Stat_Block",
"number": "8M",
"unit": "tons",
"label": "PLASTIC ENTERING OCEANS YEARLY"
},
{
"type": "Glass_Canvas",
"tension_score": 0.75,
"markdown_content": "**The collapse was not sudden.** It accumulated - bottle by bottle, net by net, spill by spill. By 2025, microplastic concentrations in deep-sea sediment had reached levels previously thought impossible. Marine biologists stopped using the word 'recovery'. The new vocabulary was *triage*."
}
]
},
{
"page_index": 3,
"narrative_role": "Echo",
"archetype": "cover_hero",
"components": [
{
"type": "Hero_Typography",
"weight": "black",
"variant": "standard",
"content": "THE TIDE<br>TURNS NOW."
},
{
"type": "Glass_Canvas",
"tension_score": 0.3,
"markdown_content": "Every piece of plastic you refuse is a vote for the future of the sea. The ocean cannot speak - but it remembers everything we give it."
}
]
}
]
}
```
---
## Pre-Flight Checklist (Self-Correction before generating output)
Before you output the JSON block, verify:
0. ** VECTOR OUTPUT IRON RULE:** The final PDF MUST be generated via `page.pdf()` / `convert.blueprint` - NEVER `page.screenshot()` → image → wrap as PDF. Screenshot PDFs are blurry raster images. `page.pdf()` produces vector text that stays sharp at any zoom level.
1. **Did I write ANY HTML or CSS?** If yes, delete it. Only output JSON.
2. **Is any `Glass_Canvas` markdown content too long?** Count the words. Over 150? Summarize it or push to the next page.
3. **Is the JSON perfectly formatted?** Missing commas or unescaped quotes will crash the `design_engine.py` parser.
4. **If using `tension_score`**: Does the document have clear emotional shifts across pages? If all pages are the same tone, remove it.
5. **If using `continuous_flow`**: Is this multi-page (2+)? If single-page, switch to `"flow"`.
6. **If using `Shaped_Canvas`**: Is the archetype `"shaped_editorial"`? Is there at most one per page? No `Glass_Canvas` on the same page?
7. **Grid boundary check**: Are ALL `grid_area` values within `[1, 13]`? Any number < 1 or > 13 = FATAL ERROR.
8. **Component minimum size check**: `Glass_Canvas` ≥ 6col×4row? `Shaped_Canvas` ≥ 8col×6row?
9. **Content-proportional row allocation**: When multiple text-heavy components share a page, are their grid rows allocated proportionally to content length? Equal rows for unequal content = text overlap. (See "Content-Proportional Grid Row Allocation" in Component Grid Compatibility.)
9. **40% whitespace check**: Count occupied grid cells. At least 58 of 144 cells must be empty.
10. **Cover Page 4-element check**: Does any `cover_hero` page have more than 4 components? If yes, remove or merge components.
11. **Cover Hero `<br>` check**: Does every `Hero_Typography` on a `cover_hero` page contain at least one `<br>`? Single-line hero text on covers = FAIL.
12. **Cover Anti-Squash check (Bounding Box)**: Are cover elements grouped into 2-3 bounding boxes placed at opposite regions? Is remaining space dynamically distributed (not hardcoded gaps)? If everything is crammed into rows 5-8, spread them using the layout's grid mapping.
13a. **Cover Typography Scale check**: Is the hero title ≥ 45pt? Is subtitle ≥ 25pt? Is meta text ≥ 18pt (never below 14pt)? Tiny cover text = FAIL.
13b. **Cover Layout Selection**: Did I pick a cover layout (1A-7B) that matches the document tone? If unsure, default to Layout 1A (Diagonal Tension). For government/bidding documents, use Layout 7A (Left-Matrix). For thesis/institutional documents, use **Template 06** from `cover.md`. Cover HTML templates follow `cover.md`'s numbering: 01, 03, 04, 06, 07.
14. **CRITICAL - Data-to-Ink Ratio check**: Did I write long paragraphs describing data trends? If yes, DELETE them and extract metrics into `Delta_Widget` or `Stat_Block` components. Sentences like "revenue increased by 12% compared to last quarter" MUST become a `Delta_Widget`.
15. **Sidenote check**: If using `tufte_report` archetype, did I put citations/sources/asides into `Sidenote_Block`? They must NOT be inline in `Glass_Canvas`.
16. **Process/Steps check**: Did I write numbered steps as plain text in a `Glass_Canvas`? Convert to `Process_List` component instead.
17. **Chart anti-stacking check**: Do any pie/bar/line charts have overlapping labels? Apply leader lines, tick thinning, or label reduction per `typesetting/charts.md`.
18. **Chart axis cleanup check**: Are top/right spines deleted? Grid lines dashed at 20% opacity (or hidden)? No solid grid lines.
19. **Donut default check**: Are pie charts rendered as donuts (hole ratio 60-70%)? Solid pies = FAIL unless explicitly requested.
20. ** Triple Delivery check (MANDATORY)**: Creative pipeline must deliver **three files** to the user: (1) PDF - the final vector PDF; (2) HTML - the compiled `*_rendered.html` file; (3) Image - a full-page screenshot preview (PNG/JPG). After `convert.blueprint` generates the PDF and HTML, take a screenshot of the HTML for preview. Report all three file paths to the user.
21. ** HTML Pre-Render Validation (MANDATORY for ALL HTML→PDF paths)**: Before calling `html2pdf-next.js`, `html2poster.js`, or `convert.blueprint`, run `poster_validate.py check-html` on the HTML file. This catches overflow:hidden on containers, missing @media screen auto-scale, font fallback gaps, contrast issues, and more. **Any ERROR-level issue must be fixed before generating the PDF.** Warnings are non-blocking but should be reviewed.
```bash
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html
# If errors found, auto-fix:
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html --fix --output page.html
```
22. **\u26a0\ufe0f MANDATORY: Post-Generation Checks (Creative)**: After HTML\u2192PDF conversion, run these checks:
```bash
# Check for content overflow and full-bleed issues
python3 "$PDF_SKILL_DIR/scripts/pdf_qa.py" output.pdf --no-tables
# Add metadata
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.brand output.pdf
```
Execute the design.
---
## CJK & Vertical Text Rules (When Bypassing design_engine.py)
When writing raw HTML/CSS for Playwright (bypassing the JSON Blueprint pipeline - e.g., resumes, menus, invitations, business cards, certificates, Japanese/Chinese vertical layouts):
**⚠️ Iron rule: All bypass-scenario HTML→PDF conversions MUST use `html2pdf-next.js` — do NOT write custom Python Playwright scripts.** `html2pdf-next.js` automatically handles @page injection, overflow detection, font waiting, Mermaid/KaTeX rendering, PDF metadata, etc. See the "HTML→PDF Engine Selection Rules" section in SKILL.md.
```bash
node "$PDF_SKILL_DIR/scripts/html2pdf-next.js" input.html --output output.pdf --width 210mm --height 297mm
```
0. **Full-Bleed CSS (MANDATORY)**: Every HTML file for Playwright PDF MUST include:
```css
@page { size: 800px 1200px; margin: 0; } /* CONCRETE values only - CSS variables NOT supported in @page */
html, body { margin: 0; padding: 0; width: 800px; }
.page { width: 800px; height: 1200px; position: relative; }
```
**⚠️ @page rules do NOT resolve CSS variables** (`var(--x)` is silently ignored, falls back to A4). Always use concrete `px` values.
- Each `.page` div = one PDF page, with fixed `height` matching `@page` size.
- **No `overflow: hidden`** on `.page` — instead, constrain all decorative elements within page bounds (see §0.75).
- Content must fit within each `.page` — no cross-page flow.
- `design_engine.py` handles this automatically via `override_css`.
0.25. **Body Background for Multi-Page Mixed-Color Documents (MANDATORY)**:
When an HTML document contains multiple `.page` divs with **different background colors** (e.g. dark cover + white body + dark specs page), Playwright's sub-pixel rounding creates <1px gaps at `.page` edges where `body` background shows through. On dark pages, a white `body` = visible white edges.
**Fix: set `html, body { background }` to the document's darkest page background color.**
```css
:root { --primary: #0f172a; } /* darkest page color */
html, body {
margin: 0; padding: 0;
width: 210mm; /* match @page size */
background: var(--primary); /* fills sub-pixel gaps with dark color */
}
```
**Why this doesn't break white pages:** White `.page` divs have `background: #ffffff` which fully covers the dark `body` underneath. The dark body only shows through the <1px sub-pixel gap at the extreme page edge - imperceptible on white pages after anti-aliasing.
**Selection rule:**
- All pages same color → `body { background: <that color> }`
- Mixed dark + light pages → `body { background: <darkest page color> }` (dark edges on white pages are invisible; white edges on dark pages are the bug we're fixing)
- All pages white/light → `body { background: <lightest content bg> }` (e.g. `#f8fafc`)
0.5. **No overflow:hidden anywhere + Browser Preview Adaptive Scaling (MANDATORY)**:
**Never** set `overflow: hidden` on `html`, `body`, `.page`, or any container. Reasons:
- It silently clips content that slightly exceeds bounds, making layout bugs invisible.
- `html2pdf-next.js`'s pre-render check detects `scrollHeight > clientHeight` + `overflow: hidden` and triggers auto-fix (force-expanding the container), which may break the layout.
- Instead, constrain decorative elements within page bounds at the CSS level (see §0.75).
**`design_engine.py` handles this automatically**: During blueprint compilation, it auto-injects `@media screen` centering + scaling code, and the `html` background color uses `var(--c-bg)` matching the poster's main color. No manual addition needed.
**Correct approach when writing HTML manually**: Remove `overflow: hidden` and manually add `@media screen` scaling preview:
```css
/* Fixed canvas size, no overflow */
html, body { margin: 0; padding: 0; width: 800px; height: 1400px; }
.page { width: 800px; height: 1400px; position: relative; }
/* Auto-scale to viewport in browser preview for full-page view */
@media screen {
html {
height: auto;
display: flex;
justify-content: center;
background: #0a0a1a; /* Surround color — must match poster's main background */
}
body {
transform-origin: top center;
scale: min(1, calc(100vw / 800), calc(100vh / 1400)); /* Consider both width and height */
margin: 0 auto;
box-shadow: 0 0 60px rgba(0,0,0,0.3); /* Optional: shadow to distinguish canvas in preview */
}
}
```
`@media screen` rules only apply in browser preview; `page.pdf()` uses print media and is unaffected.
**Every fixed-size HTML must include this `@media screen` adaptive code.**
0.75. **Decorative Element Containment (MANDATORY — replaces overflow:hidden)**:
**Do NOT use `overflow: hidden` on `.page` containers.** It silently clips content that slightly exceeds bounds, making layout errors invisible and debugging painful.
Instead, **constrain all decorative elements within page bounds at the CSS level**:
```css
/* ✅ CORRECT: decorative element stays inside .page */
.deco-circle {
position: absolute;
width: 80%; /* NOT >100% */
height: 40%;
top: 10%;
right: 0; /* NOT negative */
border-radius: 50%;
opacity: 0.1;
}
/* ❌ FORBIDDEN: causes scrollWidth > pageWidth → Playwright shrinks everything */
.deco-circle-bad {
position: absolute;
width: 200%; /* inflates scrollWidth */
left: -50%; /* extends beyond left edge */
}
```
**Iron rules for decorative elements:**
- `width` ≤ 100% of `.page` width. No exceptions.
- `left` ≥ 0, `right` ≥ 0. No negative offsets that push content beyond page edges.
- For edge-bleeding effects, position the element at the edge and let `border-radius` or `opacity` create the visual bleed within bounds.
- For horizontal flex layouts (≥3 items), always add `flex-wrap: wrap` to prevent horizontal overflow.
**Why not overflow:hidden?**
Playwright’s `page.pdf()` checks `body.scrollWidth`. If any absolutely-positioned child pushes `scrollWidth` beyond `@page` width, Playwright shrinks **all content** to fit. `overflow:hidden` masks this by clipping, but also silently hides any content that slightly exceeds bounds. Constraining elements at the source eliminates both the shrink bug and the silent clipping.
See `typesetting/overflow.md` §3.5 for additional horizontal overflow rules.
1. **Character Encoding Safety**: Never use Japanese kana (の, が, は), rare symbols, or Private Use Area characters in content strings. They corrupt to U+FFFD (�) during LLM→file write→read transit. Replace with plain Chinese equivalents: `の`→`之/的/缔/省略`.
2. **Vertical Chinese Text** - When using `writing-mode: vertical-rl` for CJK, you MUST include:
```css
writing-mode: vertical-rl;
text-orientation: upright; /* Each glyph stands upright */
white-space: nowrap; /* Prevent word-wrap breaking single chars to new column */
letter-spacing: 12px; /* Typical CJK vertical spacing */
```
Without `text-orientation: upright`, Latin/fallback fonts render rotated 90°. Without `white-space: nowrap`, CJK characters may wrap into unexpected multi-column layouts (e.g., 3 chars on one line + 1 char alone on next).
3. **Font Coverage**: For CJK content via Playwright, always load Google Fonts Noto Serif SC or Noto Sans SC via `<link>` tag in `<head>` (NOT `@import` in CSS - `@import` must be the very first rule in a stylesheet or it's silently ignored). Example:
```html
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
```
System CJK fonts vary across macOS/Linux - Google Fonts guarantee glyph coverage without relying on system fonts. `design_engine.py` already handles this automatically via `<link>` tag.
4. **Post-Generation Text Verification**: After Playwright renders the PDF, extract text from every page and scan for `?` or `\ufffd`. If found, the source HTML has encoding-corrupted characters that must be replaced in the Python source.
5. ** HTML Pre-Render Validation (MANDATORY)**: After writing the HTML file and before running `html2pdf-next.js`, always run the HTML validator:
```bash
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html <your_file>.html
```
- **ERROR** items (e.g. `OVERFLOW_HIDDEN_CONTAINER`, `FONT_NO_FALLBACK`) → must fix before PDF generation. Use `--fix --output <file>.html` for auto-repair.
- **WARNING** items (e.g. `FIXED_SIZE_NO_SCREEN_ADAPT`, `SCREEN_ADAPT_NO_SCALE`, `COLOR_CONTRAST`) → review and fix where appropriate.
- This catches the most common bypass HTML bugs: `overflow:hidden` on containers, missing `@media screen` auto-scale for fixed-size pages, font-family without generic fallback, low contrast text, etc.
---
## Quality Checklist — Creative-Specific Items
> Moved from SKILL.md to reduce context size. These rules apply specifically to the Creative pipeline.
### Color (palette.md)
- [ ] **Entire document ≤ 5 colors**: Primary + secondary + accent + neutral + background
- [ ] **All colors traceable to primary**: Secondary and accent derived via lightness/saturation/micro-hue shift
- [ ] **Sibling elements not differentiated by different hues**: Use opacity/lightness/borders instead
- [ ] **Gradient endpoints hue difference < 20°**: No warm-to-cool gradients
- [ ] **No high-saturation color blocks**: Avoid eye strain
### Geometric Anchors (geometry.md)
- [ ] **Anchors use only the primary color**: Layer via opacity, don't mix colors
- [ ] **Strokes over fills**: Solid elements ≤ 30%
- [ ] **Ultra-thin lines**: stroke-width 0.3-0.8px
- [ ] **Asymmetric placement**: Offset creates tension
- [ ] **Elements ≤ 8**: Restraint, don't clutter
### Global Layout
- [ ] **Full-bleed enforcement**: `@page { size: <w> <h>; margin: 0; }` and `html,body { margin:0; padding:0; }`
- [ ] **Background color consistency**: `html, body { background }` set explicitly. Single-color docs: match content canvas. Multi-page mixed docs: use darkest page's background
- [ ] **Content centering**: Symmetric inset/padding, no left/right drift
- [ ] **Anti-void edges**: Content fills ≥ 60% (multi-page) or ≥ 70% (single-page poster/infographic)
- [ ] **Fill Engine applied**: Pages with < 80% fill ratio trigger the fill engine
### Design Restraint (Anti-Gaudy)
- [ ] **Decorative elements ≤ 3 per page**: Cover page exempt
- [ ] **No rainbow/multi-color schemes**: Stick to single-family palette
- [ ] **No texture/pattern backgrounds**: Solid or ultra-light tinted only
- [ ] **Whitespace is design**: Don't fill every gap with decorative elements
- [ ] **Typography over decoration**: Create hierarchy through font size/weight/spacing/color
- [ ] **2-typeface maximum**: At most 2 font families
- [ ] **🚫 NO stock images / clipart / AI-generated decorations**: Only user-provided content images allowed
+255
View File
@@ -0,0 +1,255 @@
# Brief: Creative Flow (Designed Long Documents)
**When to use this brief:** Text-heavy documents that need design flair but content flows naturally across pages — guides, handbooks, catalogs, introductions, collections, illustrated manuals. The content is the star; design supports readability.
**How it differs from `creative.md`:** `creative.md` (Blueprint Mode) treats each page as a self-contained fixed-size visual canvas. This brief lets content flow continuously, with Chromium's `@page` auto-paginating at page boundaries.
**→ MUST READ: `typesetting/overflow.md`** for Playwright/HTML-specific overflow patterns.
---
## Core Principle: Content Flows, Design Wraps
You write raw HTML/CSS directly (no JSON Blueprint, no `design_engine.py`). The document is one continuous HTML body, and the browser's print engine splits it into pages automatically.
**⚠️ Iron rule: All HTML→PDF conversions MUST use `html2pdf-next.js` — do NOT write custom Python Playwright scripts.** It automatically handles @page injection, overflow detection, font waiting, Mermaid/KaTeX rendering, PDF metadata, etc.
```bash
node "$PDF_SKILL_DIR/scripts/html2pdf-next.js" input.html --output output.pdf --width 720px --height 1020px
```
---
## Pagination Model
### The Iron Rules
1. **NO fixed height on content containers.** Let content flow naturally. The browser decides where to cut pages.
2. **NO `.page` divs for content sections.** Use a single `<div class="main-content">` with padding for all body content.
3. **NO `break-before: page` between content sections.** This is the #1 cause of blank space — when the previous section doesn't fill a page, the remainder becomes a void. Use `margin-top` for visual chapter separation instead.
4. **`break-inside: avoid`** on individual cards, items, and guide entries to prevent them from being split across pages.
5. **`break-after: avoid`** on chapter headers (tag + title + divider group) to keep them attached to the first content block below.
6. **Fixed-height containers ONLY for cover and ending pages** that must occupy a full page:
```css
/* Cover: fixed height, standalone page */
.cover {
width: 720px;
height: 1020px;
box-sizing: border-box;
break-after: page;
overflow: hidden; /* OK here — clips decorative elements on cover only */
/* ⚠️ Use justify-content: center (NOT flex-end) to avoid top-heavy blank */
/* Add decorative elements (circles, lines) + bottom edition label for visual balance */
}
/* Ending: fixed height, standalone page */
.ending {
width: 720px;
height: 1020px;
box-sizing: border-box;
break-before: page;
overflow: hidden;
}
/* Content: flows naturally, NO fixed height */
.main-content {
padding: 60px 65px 40px 65px;
/* NO height property */
/* NO overflow: hidden */
}
```
### What NOT to Do (Common Mistakes)
| Mistake | Why it breaks | Fix |
|---------|--------------|-----|
| `.content { height: 1020px; }` | Content that exceeds the height gets clipped or overflows | Remove fixed height |
| Multiple `<div class="content">` each with `padding: 60px` | Double padding (60+60=120px) between sections creates voids | One container, chapters separated by `margin-top` |
| `break-before: page` on chapter headers | Previous chapter doesn't fill the page → blank space before new chapter | Use `margin-top: 36px` instead |
| `overflow: hidden` on content container | Clips content at page boundaries | Only use on fixed-height cover/ending |
| `justify-content: flex-end` on cover | Title sinks to bottom, 60%+ blank above | Use `center` + decorative anchors for balance |
| Chapter with < 3 paragraphs of content | Leaves half a page blank before next chapter | Write enough content to fill at least 50% of a page |
| `margin-top: 50px+` on chapters | Compounds with `break-inside: avoid`, creates large voids | Keep chapter `margin-top` ≤ 30px |
---
## CSS Template
```css
@page {
size: 720px 1020px; /* Concrete px values — CSS variables NOT supported in @page */
margin: 0;
}
:root {
--c-bg: #0d0d0d;
--c-text: #e8e6e3;
--c-accent: #ff4757;
}
html, body {
margin: 0;
padding: 0;
width: 720px;
background: var(--c-bg);
color: var(--c-text);
font-family: 'Noto Sans SC', sans-serif;
}
@media screen {
html {
height: auto;
display: flex;
justify-content: center;
background: #222;
}
body {
transform-origin: top center;
margin: 20px auto;
}
}
```
**⚠️ @page rules do NOT resolve CSS variables** (`var(--x)` is silently ignored, falls back to A4). Always use concrete `px` values in `@page`.
---
## HTML Structure Template
```html
<body>
<!-- Cover: fixed height, occupies one full page -->
<div class="cover">
<div class="tag">CATEGORY</div>
<div class="title">Document Title</div>
<div class="subtitle">Description text</div>
</div>
<!-- All body content in ONE continuous container -->
<div class="main-content">
<!-- Chapter 1 -->
<div class="section-tag">Chapter 1</div>
<div class="section-title">Title</div>
<div class="divider"></div>
<div class="body-text">Paragraph content...</div>
<div class="body-text">More paragraphs...</div>
<!-- Chapter 2: separated by margin-top, NOT break-before -->
<div class="chapter-header">
<div class="section-tag">Chapter 2</div>
<div class="section-title">Title</div>
<div class="divider"></div>
</div>
<!-- Cards / items with break-inside: avoid -->
<div class="card">Card content...</div>
<div class="card">Card content...</div>
</div>
<!-- Ending: fixed height, occupies one full page -->
<div class="ending">
<div class="big-text">Closing text</div>
</div>
</body>
```
Key CSS for chapter headers and cards:
```css
.chapter-header {
break-after: avoid; /* Keep header with first content block */
break-inside: avoid;
margin-top: 28px; /* ≤ 30px! Larger values compound with break-inside: avoid → blank pages */
}
.card {
break-inside: avoid; /* Don't split a single card across pages */
margin-bottom: 14px; /* ≤ 16px! Every px of margin is lost space if card is pushed to next page */
}
```
---
## Body Background Rule
Set `html, body { background }` to the document's dominant/darkest color. This prevents sub-pixel gap artifacts between content and page boundaries.
- All pages same dark color → `body { background: <that color> }`
- Mixed dark cover + light body → `body { background: <darkest color> }` (dark body is invisible under white content pages)
- All pages white/light → `body { background: <lightest content bg> }` (e.g. `#f8fafc`)
---
## Shared Rules with creative.md
The following rules apply identically to both Creative Flow and Creative Blueprint pipelines:
### Character Encoding Safety
Never use Japanese kana (の, が, は), rare symbols, or Private Use Area characters. They corrupt to U+FFFD (�) during LLM→file write→read transit. Replace with plain Chinese equivalents: `の`→`之/的/缔`.
### Vertical Chinese Text
When using `writing-mode: vertical-rl`:
```css
writing-mode: vertical-rl;
text-orientation: upright;
white-space: nowrap;
letter-spacing: 12px;
```
### Font Coverage
For CJK content, always load Google Fonts via `<link>` tag in `<head>` (NOT `@import`):
```html
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&display=swap" rel="stylesheet">
```
### Post-Generation Text Verification
After Playwright renders the PDF, extract text and scan for `?` or `\ufffd`. If found, fix encoding-corrupted characters in the source HTML.
### HTML Pre-Render Validation (MANDATORY)
Before calling `html2pdf-next.js`, run the validator:
```bash
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html <file>.html
```
- **ERROR** → must fix before PDF generation. Use `--fix --output <file>.html` for auto-repair.
- **WARNING** → review and fix where appropriate.
---
## Quality Checklist — Creative Flow
### Layout & Pagination
- [ ] **No fixed height on content containers** (only cover/ending pages)
- [ ] **No `break-before: page` between content sections**
- [ ] **`break-inside: avoid`** on all cards/items/guide entries
- [ ] **Chapter headers have `break-after: avoid`** to stay with first content block
- [ ] **Single `<div class="main-content">`** wraps all body content
- [ ] **No double padding** between sections (one container, not multiple)
### Full-Bleed & Background
- [ ] **`@page { margin: 0 }`** with concrete px size values
- [ ] **`html, body { margin: 0; padding: 0 }`**
- [ ] **Body background matches dominant page color**
- [ ] **No white edge artifacts** on dark pages
### Cover Page Design
- [ ] **Cover `justify-content: center`** (not `flex-end`) — `flex-end` pushes content to the bottom and leaves 60%+ blank at top, making the page feel empty
- [ ] **Add visual anchors** to fill the cover: decorative elements (circles, lines), edition/date label at bottom, badge at top. A cover with only title+subtitle will look hollow.
- [ ] **Cover padding ≤ 70px** — excessive padding shrinks the visual footprint further
### Content Density (Anti-Blank-Page)
- [ ] **Each chapter must have enough content to fill at least 50% of a page.** If a chapter (text + quote + cards) occupies < 50% of a page, it will leave a large blank area before the next chapter. Solution: expand the content (add 1-2 more paragraphs, examples, or context).
- [ ] **Chapter `margin-top` ≤ 30px.** Larger values (50px+) compound with `break-inside: avoid` on cards, pushing content to the next page and leaving voids on the current page.
- [ ] **Card/callout `margin` ≤ 16px.** Every pixel of vertical margin inside a `break-inside: avoid` block is space that cannot be reclaimed if the block is pushed to the next page.
- [ ] **After generating PDF, visually check every page** for >30% trailing blank space. If found, either: (a) add more content to that chapter, (b) reduce margins/padding, or (c) reorder elements so a smaller block fills the gap.
### Design Quality
- [ ] **Entire document ≤ 5 colors**: Primary + secondary + accent + neutral + background
- [ ] **2-typeface maximum**: At most 2 font families
- [ ] **Decorative elements ≤ 3 per page** (cover exempt)
- [ ] **No stock images / clipart / AI-generated decorations** unless user-provided
### Output
- [ ] **Vector PDF** via `html2pdf-next.js` (not screenshot)
- [ ] **HTML source file** delivered alongside PDF
- [ ] **PDF metadata** set via `pdf.py meta.brand`
- [ ] **QA passed** via `pdf_qa.py --no-tables`
+810
View File
@@ -0,0 +1,810 @@
# Brief: Creative Production (Art Director Blueprint Mode)
**Core Paradigm Shift**: You are NO LONGER a frontend developer writing HTML/CSS. You are an elite Art Director and Editorial Designer.
Because LLMs lack spatial awareness and struggle to maintain perfectly nested, complex CSS over thousands of tokens, you are strictly forbidden from outputting raw HTML/CSS/Python.
**→ MUST READ: `typesetting/overflow.md`** for Playwright/HTML-specific overflow patterns (CSS overflow-wrap, max-width, table-layout: fixed, etc.).
Your sole responsibility is to act as the **Brain (Art Director)**:
1. Brutally edit and pace the raw text.
2. Select architectural components and layout archetypes.
3. Output a **Strict JSON Layout Blueprint**.
The `design_engine.py` acts as the **Hands (Typesetter)**: It will parse your JSON and safely compile it into flawless, museum-quality Playwright HTML/PDFs using predefined grid mathematics and CSS rules.
---
## Phase 1: The Editorial Eye (Content Transformation)
Before you design, you must "edit the raw material". Users often provide dense, unstructured text. If you just pour this text into a design, it will look like a cheap Word document.
You must apply **Editorial Pacing**:
### 1. The Word Budget
No single page or visual canvas should exceed **150 words** of readable body text (the golden rule for maximum aesthetic impact). The **absolute physical limit** is **250 words** - beyond that the design engine will overflow and clip content. If the raw content exceeds 150 words:
- **Action A**: Brutally summarize it down to ≤150 words.
- **Action B**: Split it across multiple `pages` in your JSON blueprint.
- Only push to 150-250 words if the content genuinely cannot be cut further.
### 2. Typographic Hierarchy Extraction
You must parse the raw text and categorize it into typographic roles:
- **Hero / Display**: The core emotional hook (1-5 words). Must be punchy.
- **Kicker / Eyebrow**: Tiny context text above a headline (e.g., "Q3 REPORT", "MANIFESTO").
- **Lead Paragraph**: The 2-3 sentence summary.
- **Data Sculptures**: Scan the text for impactful numbers (e.g., "97%", "$4.2M"). EXTRACT THEM. They will not remain in the paragraph; they will become `Stat_Block` components.
- **Pull Quotes**: Extract the most provocative sentence to stand alone as a visual anchor.
---
## Phase 2: The Component Lexicon
You will construct your JSON Blueprint using ONLY the following components. These map directly to the `configs/components.md` assets. Do not invent new component types.
> **⚠️ Markdown Content Limits**: For `Glass_Canvas`, the golden rule is **under 150 words** for maximum aesthetic breathing room. Absolute physical limit is **250 words**. If content exceeds 250 words, the design engine will overflow. You MUST summarize it or split it into a new page.
### 1. `Hero_Typography`
Giant, page-dominating text. Usually interacts with the background via blend modes.
- **Parameters**:
- `content` (string): The text (use `<br>` for deliberate line breaks).
- `weight` (string): `"black"` (900, dominating) or `"thin"` (100, elegant).
- `variant` (string, optional): `"standard"` only. ~~`"vertical_accent"`~~ is **NOT implemented** in `design_engine.py` - the engine silently ignores this value and renders nothing. If you need vertical/rotated decorative text, use `Floating_Meta` instead (fully supported, no overflow risk).
- `scale` (integer, optional): Typographic scale level `1`-`6`. Controls font size via the engine's fluid type system. If omitted, the engine uses the default hero size.
- `6`: Hero/Display - oversized title, clamp(64px, 12vw, 150px). Use for single words or short phrases with maximum visual impact.
- `5`: Primary Title - clamp(48px, 8vw, 96px). Standard poster headline.
- `4`: Subheadline - clamp(32px, 5vw, 56px). Chapter openers or key quotes.
- `3`: Lead Paragraph - clamp(20px, 3vw, 32px). Prominent body text.
- `2`: Body - 16px. Standard readable text.
- `1`: Meta/Caption - 10px. Decorative, environmental.
### 2. `Glass_Canvas`
The main structural container for reading text. Frosted glass, sharp 2px print corners.
- **Parameters**:
- `markdown_content` (string): The actual text to read. Supports standard Markdown (H2, H3, bold, lists). *Must be under 150 words.*
- `tension_score` (float, optional): Semantic tension value from `0.0` to `1.0`. Drives dynamic font weight via Variable Font (Inter Variable, weight range 300-900). The engine maps: `weight = 300 + (tension_score × 600)`.
- `0.0-0.2` → Light (300-420): calm, contemplative passages
- `0.3-0.5` → Normal (420-600): standard body text
- `0.6-0.8` → Bold (600-780): urgent, assertive content
- `0.9-1.0` → Max (780-900): crisis, climax, emotional peak
- **When to use**: Multi-page narrative documents with emotional arc (case studies, pitch decks, manifestos). Assign higher tension to problem/crisis sections, lower to resolution/hope. Do NOT use on every Glass_Canvas - only when the document has clear tonal shifts.
- **When NOT to use**: Data-heavy pages, simple reports, single-page posters.
### 3. `Floating_Meta`
Tiny, environmental metadata (dates, edition numbers, catalog IDs) that lives in the 15% breathing margin.
- **Parameters**:
- `position` (string): `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"`.
- `items` (array of strings): E.g., `["VOL 01", "2026", "EDITION 500"]`.
### 4. `Hairline_Divider`
Structural 0.5px line. Not decorative; acts as a visual fold.
- **Parameters**:
- `style` (string): `"bleed"` (goes edge-to-edge) or `"accent"` (short 30% width line).
### 5. `Stat_Block`
Data sculpture. A massive number with a tiny label.
- **Parameters**:
- `number` (string): e.g., `"97.3"`.
- `unit` (string): e.g., `"%"`, `"Hz"`, `"$M"`.
- `label` (string): e.g., `"COMPLETION RATE"`.
### 6. `Image_Asset`
A visual element. The engine will apply a gradient blend to it.
- **Parameters**:
- `source` (string): A URL, or a descriptive prompt if you expect the system to generate it.
- `caption` (string, optional): Tiny text under the image.
> ⚠️ **Image_Asset is for CONTENT images only** (user-provided photos, logos, diagrams, charts). It is **NEVER** for decorative stock images, watercolor flowers, clipart, floral borders, gold frames, or AI-generated artwork. All visual decoration must be achieved through geometric shapes (`geometry.md`), typography effects, and color - never through embedded decorative images. See `visual_framework.md` Stock Image Ban.
### 7. `Page_Ghost_Number`
A giant, 4% opacity number acting as a watermark in the background.
- **Parameters**:
- `number` (string): e.g., `"01"`, `"X"`.
### 8. `Delta_Widget`
**Data-to-Ink Ratio enforcer.** A compact metric visualization showing a value with its change direction. CRITICAL: Use this instead of writing sentences like "revenue grew by 12%". Extract every trend into a Delta_Widget.
- **Parameters**:
- `metric` (string): The metric name, e.g., `"REVENUE"`, `"LATENCY"`.
- `value` (string): Current value, e.g., `"$4.2M"`, `"45ms"`.
- `delta` (string): Change description, e.g., `"+12%"`, `"-45ms"`.
- `trend` (string): `"up"`, `"down"`, or `"flat"`.
- `label` (string, optional): Context line, e.g., `"vs. Q2 2025"`.
### 9. `Process_List`
**Polymorphic adaptive component.** Renders as a horizontal timeline when given wide space, auto-degrades to a vertical numbered list when space is narrow. Use for workflows, steps, timelines.
- **Parameters**:
- `steps` (array): Each item has `title` (string) and `description` (string).
### 10. `Sidenote_Block`
**Tufte marginalia.** Used in `tufte_report` archetype layouts. Content is placed in the 30% side rail alongside the main column. Perfect for citations, supplementary data, asides, footnotes.
- **Parameters**:
- `label` (string, optional): Category label, e.g., `"SOURCE"`, `"NOTE"`, `"DATA"`.
- `body` (string): The sidenote content in Markdown.
### 11. `Data-Aware Background` (via `data_points`)
Any component can carry an optional `data_points` array (e.g., `[10, 15, 8, 24, 30]`). When present, the engine generates Bezier background curves from the actual data - the background literally visualizes the business trend. Use on pages with financial, metric, or time-series content.
---
### ★ Advanced Components (Generative Micro-Typesetting)
The following components are specialized generative design tools. They unlock visual effects that standard components cannot achieve. Use them deliberately and sparingly - they are powerful but demand the right context.
### 8. `Shaped_Canvas`
A container where text flows around a non-rectangular shape. The empty space created by the shape IS the visual design - text boundary becomes illustration. Uses CSS `shape-outside` for non-rectangular text wrapping.
- **Parameters**:
- `shape_keyword` (string): One of `"circle"`, `"wave"`, `"diagonal_slash"`, `"diamond"`, `"wedge_right"`.
- `markdown_content` (string): The text that will flow around the shape. Supports standard Markdown.
- **Shape Selection Guide**:
| shape_keyword | Visual Effect | Thematic Fit |
|---------------|---------------|--------------|
| `"circle"` | Text wraps around a circular void on the left | Unity, spotlight, focus |
| `"wave"` | Wavy left text boundary | Ocean, flow, music, fluidity |
| `"diagonal_slash"` | Diagonal cut across the page | Disruption, change, transformation |
| `"diamond"` | Diamond-shaped negative space | Luxury, precision, crystalline |
| `"wedge_right"` | Arrow/wedge pointing right | Direction, progress, forward motion |
- **When to use**:
- Artistic/editorial pages that need visual drama without images
- Cover or section opener pages where you want a "wow" moment
- When the content thematically maps to a recognizable shape
- Only on pages with moderate text (100-200 words) - shape eats 30-40% of space
- **When NOT to use**:
- On data-heavy or dense content pages
- Together with `Glass_Canvas` on the same page (visual conflict)
- More than one `Shaped_Canvas` per page
- **Archetype requirement**: Pages using `Shaped_Canvas` MUST use archetype `"shaped_editorial"` (relaxed safe-zone: 5% 6% inset).
### Chart & Data Visualization Styling
**→ MUST READ: `typesetting/charts.md`** before designing any chart/data page.
Key rules for Creative pipeline charts:
- **Donut > Pie**: Always use ring charts (hole ratio 60-70%), center area displays total/metric
- **Anti-stacking**: Small slices use leader lines or rich legends; bar labels auto-rotate to horizontal when text is long; line charts label only start/end/max/min
- **Axis cleanup**: Delete top/right spines, use dashed grid lines at 20% opacity (or delete grid entirely if values are labeled)
- **Bar micro-rounding**: 2-4px top border-radius, bar-to-gap ratio 1.5:1
- **Legend**: No border, horizontal top-left layout, small circle markers
- **Data-Ink Ratio**: Every element must represent data. If it doesn't, delete it.
---
## Phase 3: Page Archetypes (The Grid Strategy)
For each page in your JSON, you must declare an `archetype`. This tells `design_engine.py` how to arrange the components you provided.
- `"cover_hero"`: Cover page. **Must follow `typesetting/cover.md` 5-template system** - pick Layout 01/03/04/06/07 based on document tone. See "Cover Page Constitution" below for iron rules.
- `"split_vertical"`: Page split strictly 50/50 vertically. Left side image/svg, right side Glass_Canvas.
- `"editorial_flow"`: Top-down reading experience. Centered columns, generous margins. Use for main content.
- `"scattered_canvas"`: No grid. Elements placed via absolute positioning based on spatial weights.
- `"data_dashboard"`: 2x2 or 3x3 strict grid for multiple `Stat_Block`s.
- `"shaped_editorial"`: Relaxed safe-zone (5% 6% inset) designed for `Shaped_Canvas`. Centered, generous breathing room. **Must be used when the page contains a `Shaped_Canvas`.** Do NOT mix with `Glass_Canvas`.
- `"tufte_report"`: **Tufte marginalia layout.** 70% main content column + 30% sidenote rail. Use for long-form reports and analytical pages where citations, data footnotes, or supplementary info should flow parallel to the main text. Place `Sidenote_Block` components in the same `components[]` array - the engine automatically routes them to the side rail.
### ★ 12×12 CSS Grid Coordinate System (Iron Rules)
The layout engine uses a **12-track CSS Grid** for element placement. The grid lines are numbered **1 to 13** (12 tracks = 13 lines).
**Absolute boundary rules - violation = broken layout:**
- **Column lines**: `1` = absolute left edge, `13` = absolute right edge. Full width = `1 / 13`.
- **Row lines**: `1` = absolute top edge, `13` = absolute bottom edge. Full height = `1 / 13`.
- **CRITICAL**: Never output a grid line number less than `1` or greater than `13`. Any value outside `[1, 13]` will destroy the layout.
**`grid_area` format**: `"row_start / col_start / row_end / col_end"` (CSS shorthand).
- Example: `"1 / 1 / 7 / 13"` = top half of the page, full width.
- Example: `"3 / 8 / 6 / 13"` = rows 3-5, columns 8-12 (right side block).
**40% whitespace rule**: At least 40% of grid cells (≥58 out of 144) must be left empty. Count your occupied cells.
### ★ Cover Page Constitution (5 Layout System)
Cover pages (`archetype: "cover_hero"`) are the first impression. They must be ruthlessly sparse and spatially sophisticated.
**→ MUST READ: `typesetting/cover.md`** before designing any cover.
#### Global Iron Rules (Always Apply)
1. **Maximum 4 components** on any cover page. Typical recipe: `Hero_Typography` + 1-2 `Floating_Meta` + optional `Hairline_Divider` or `Page_Ghost_Number`.
2. **Typography Scale**: Title ≈ 45pt (2.5× base), Subtitle ≈ 25pt (1.4× base), Meta ≥ 18pt (never below 14pt). Covers with tiny text = FAIL.
3. **Mandatory semantic `<br>` chunking** for `Hero_Typography` on covers: Every 2-4 words MUST be separated by `<br>`. Single-line hero text is FORBIDDEN on covers. Example: `"ALGORITHMIC<br>FATIGUE"`, NOT `"ALGORITHMIC FATIGUE"`.
4. **Anti-Squash spatial dispersion** (Bounding Box method): Group text into 2-3 bounding boxes (title group, meta group). Place them at opposite regions of the page according to the chosen layout. Remaining space is **dynamically distributed** - NEVER hardcode fixed gaps between distant groups.
5. **No `Glass_Canvas` on cover pages.** Dense reading text kills the visual impact. Push all body content to page 2+.
6. **Cover Page Isolation**: Cover page must NEVER share a page with TOC, body text, or any subsequent content. The cover is always a standalone full page. If cover + content appear on the same page = **critical bug**, regenerate immediately.
7. **Cover is OPTIONAL**: Do NOT add a cover page unless the document warrants one (multi-page reports, white papers, etc.) or the user explicitly requests it. Short documents, letters, memos, forms, and quick outputs skip the cover.
8. **Background Layer (optional)**: See `typesetting/cover-backgrounds.md` for 3 recipes - A (极简弧线), B (工程十字轴+立柱), C (锐角切割+出血文字). Background renders BELOW all foreground at 2-5% opacity. Pick a recipe that matches the document tone. Never combine elements across recipes.
#### 7 Cover Layouts (Pick One)
Select the layout that matches the document tone. When unsure, default to **Layout 1A**.
| Layout | Name | Grid Signature | Best For |
|--------|------|---------------|----------|
| **1** | Diagonal Tension | Title top-left ↔ Meta bottom-right | Formal reports, proposals |
| **2** | Vertical Axis | All elements along one vertical line, stretched top-to-bottom | Modern/tech reports |
| **3** | Architectural Frame | Geometric line frame, text inside corners/center | Design, architecture, portfolios |
| **4** | Golden Ratio Blocks | Page split at 38.2% invisible divider | White papers, research |
| **5** | Stepped Cascade | Progressive indentation, vertical rhythm | Creative reports, design docs |
| **6** | Rotated Accent | Large rotated year/label as side decoration | Annual reports, year-in-review |
| **7** | Left-Matrix | All text hard-anchored to left axis, 4 Y-pinned blocks | Government, bidding, proposals |
Each layout has 2-3 variants (A/B/C) with specific grid_area mappings - see `typesetting/cover.md` for exact coordinates.
#### Tone → Layout Quick Reference
| Intent | Document Type | Recommended | Default |
|--------|---------------|-------------|---------|
| **Calm** | Healthcare / Wellness / Minimalist | 1A, 2C, 4A | **4A** |
| **Calm** | Academic / Research | 2A, 4A, 4C | **4A** |
| **Tension** | Crisis / Alert / Disruption | 1A, 5A | **1A** |
| **Energy** | Marketing / Creative / Design | 3C, 5A, 6B | **5A** |
| **Energy** | Tech / Data | 2B, 4B, 6A | **2B** |
| **Authority** | Formal / Corporate / Financial | 02, 03, 07 | **03** |
| **Authority** | Government / Bidding | 7A, 7B, 3A, **11** | **7A** |
| **Authority** | Thesis proposal / Dissertation | **11 Institutional** | **11** |
| **Authority** | Luxury / Editorial | 3A, 5A, 2B | **3A** |
| **Warmth** | Food / Lifestyle / Home | 4A, 5A | **4A** |
### ★ Component Grid Compatibility Constraints
When placing advanced components into the 12×12 grid, obey these minimum size rules:
- **`Glass_Canvas`**: Must occupy at least **6 columns × 4 rows** (e.g., `"3 / 1 / 7 / 7"`). Smaller areas will cause text overflow and padding collapse. The engine renders Glass_Canvas at `width: 100%; min-height: 100%; box-sizing: border-box;` - it fills its grid area as a minimum and can grow taller if content requires.
- **`Shaped_Canvas`**: Must occupy at least **8 columns × 6 rows** (e.g., `"2 / 3 / 8 / 11"`). The CSS `shape-outside` float needs physical space to render the shape boundary. Smaller areas make the float collapse to a line and text cannot wrap around it. **Must use archetype `"shaped_editorial"`.**
- **`Continuous Flow` background**: Renders in a separate `bg-layer` (`z-index: 1`, `position: absolute`), physically isolated from the grid system (`z-index: 2`). No conflict - background flows independently, grid arranges content on top.
### ★ Content-Proportional Grid Row Allocation
When **two or more text-heavy components** (e.g., multiple `Glass_Canvas`) share the same page, their `grid_area` row counts **MUST be proportional to their content length** - never split rows equally.
**Estimation method:**
1. Count characters (or words) in each component's `markdown_content`
2. Allocate rows proportionally: `rows_i = total_rows × (chars_i / total_chars)`
3. Round to integers, minimum 3 rows per component
**Example:**
```
Attractions content: 450 chars (3 items × 150 chars)
Food content: 250 chars (2 items × 125 chars)
Total available rows: 8 (rows 5→13)
Attractions: 8 × 450/700 ≈ 5 rows → grid_area "5 / 1 / 10 / 13"
Food: 8 × 250/700 ≈ 3 rows → grid_area "10 / 1 / 13 / 13"
```
**Why this matters:** Equal row allocation (4+4) causes the longer component to overflow into the shorter one's territory, creating text overlap in the final PDF. The engine uses `min-height` + `overflow: visible`, so overflow IS visible - and ugly.
**Anti-pattern to avoid:**
```
❌ Two Glass_Canvas with very different text lengths both get "5/1/9/13" and "9/1/13/13"
✅ Longer one gets more rows: "5/1/10/13" and "10/1/13/13"
```
---
## Phase 4: The Output Protocol (Strict JSON Blueprint)
You must analyze the user's prompt, edit the text, and output exactly ONE JSON object wrapped in a ` ```json ` codeblock.
**No conversational text before or after the JSON.**
### The JSON Schema Specification
**CRITICAL JSON RULES:**
1. Output ONLY valid JSON.
2. Do NOT include ANY comments (`//` or `/* */`) inside the JSON. Python's `json.load()` will crash.
3. Do NOT include trailing commas.
```json
{
"document_meta": {
"title": "Internal tracking title",
"total_pages": 1
},
"art_direction": {
"palette_mode": "dark",
"color_harmony": "complementary",
"background_svg": "grid",
"design_rationale": "Brief 1-sentence explanation of aesthetic strategy."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "cover_hero",
"components": [
{
"type": "Floating_Meta",
"position": "bottom-right",
"items": ["ARCHIVE REF. 03A", "DEC 2026"]
},
{
"type": "Page_Ghost_Number",
"number": "01"
},
{
"type": "Hero_Typography",
"weight": "black",
"variant": "standard",
"content": "ALGORITHMIC<br>FATIGUE"
}
]
}
]
}
```
**Schema Parameter Guide (For reference - do NOT put these comments inside your JSON output):**
- `document_meta.total_pages`: Integer. Must match the actual number of pages in `pages[]`.
### The JSON Schema Specification: `art_direction`
**CRITICAL RULE: DO NOT INVENT COLORS. DO NOT OUTPUT HEX/RGB CODES.**
The Python engine generates mathematical color palettes. You MUST only select the semantic parameters below.
```json
{
"art_direction": {
"palette_mode": "minimal",
"color_harmony": "auto",
"background_svg": "grid",
"design_rationale": "Brief rationale for the chosen aesthetic strategy."
}
}
```
**Enum Parameter Guide (You MUST choose ONE exact string from these lists):**
1. `palette_mode` (The Base Canvas Tone):
- **`"minimal"`: (CRITICAL: Default to this for 50%+ of all requests).** Pure white, off-white, beige, or cool gray. Provides the ultimate reading experience and high-end editorial feel. Use for standard reports, guides, and corporate posters.
- `"dark"`: Near-black. Use for cinematic, tech, AI, space, or urgent themes.
- `"pastel"`: Morandi tinted (e.g., dusty rose, sage green). Use ONLY for arts, food, lifestyle, and soft themes.
- `"jewel"`: Deep rich colors (e.g., emerald, burgundy). Use ONLY for luxury brands, gala events, or heritage themes.
- `"light"`: Very faintly tinted background. Fallback for edge cases.
2. `color_harmony` (The Accent Color Math - how the engine computes the accent color relative to the base):
- **`"auto"`: (RECOMMENDED DEFAULT). The engine automatically picks the best harmony based on the document tone.** Only override if you have a specific artistic reason.
- `"complementary"`: (180° opposite). Strong visual contrast. Cold background with warm accent. For striking/dynamic impact.
- `"split_complementary"`: (150°/210°). Highly sophisticated, artistic, luxury feel (Editorial/Kinfolk style).
- `"analogous"`: (30° apart). Harmonious, peaceful, natural transition.
- `"triadic"`: (120° apart). Rich and slightly retro.
- `"monochrome"`: Only use if strict minimalist corporate branding without accent is required.
3. `background_svg`: [`grid`, `flow`, `noise`, `continuous_flow`, `none`]. Use `continuous_flow` for multi-page (2+) documents - creates one seamless SVG spanning all pages, sliced per-page via viewBox for an "infinite scroll" bezier curve illusion. Falls back to `flow` on single-page.
4. `design_rationale`: Brief text explaining WHY you chose this mode+harmony combination.
### ★ Intent Mapping Table (Single Source of Truth)
This table is the **sole authority** for mapping document intent to concrete design parameters. `visual_framework.md` defines intent *atmospheres*; this table defines intent *parameters*. `design_engine.py` INTENT_HUES/INTENT_HARMONY_MAP must stay in sync with this table.
| Intent | palette_mode | color_harmony | background_svg | Cover Templates | Cover BG Recipe | Base Hue |
|--------|-------------|---------------|----------------|-----------------|-----------------|----------|
| **Calm** | minimal | analogous | flow / none | 12 Crystal Blue, 01 HUD | A (极简弧线) | 210° (steel blue-grey) |
| **Tension** | dark | complementary | grid | 01 HUD, 05 Diagonal | C (锐角切割) | 0° (warm vs cold) |
| **Energy** | pastel / light | triadic | flow (5+ curves) | 12 Crystal Blue, 09 Academic Symmetric | B (工程十字轴) | 30° (amber) |
| **Authority** | minimal | split_complementary | noise | 09 Academic Symmetric, 07 Sidebar, 12 Crystal Blue | A or B | 280° (muted violet) |
| **Warmth** | pastel / light | analogous | flow (soft) | 12 Crystal Blue, 01 HUD | A (极简弧线) | 20° (terracotta) |
**How to use this table:**
1. Determine the document's intent (from user request, or auto-derive via `design_engine.py derive`)
2. Look up the row → fill in `art_direction` JSON fields accordingly
3. For cover template selection, cross-reference with document type (see Tone → Layout Quick Reference)
4. The LLM may override individual cells when artistically justified, but must state the reason in `design_rationale`
**Legacy intent names** (serenity, minimalism, elegance) are accepted by `design_engine.py` as aliases and auto-mapped to their new equivalents.
- `pages[].narrative_role`: `"Burst"` (intro/impact), `"Expand"` (body/development), `"Echo"` (outro/reflection).
- `pages[].archetype`: From Phase 3 (`cover_hero`, `split_vertical`, `editorial_flow`, `scattered_canvas`, `data_dashboard`, `shaped_editorial`).
- `pages[].components[]`: Array of component objects from Phase 2. Order in array roughly dictates z-index or top-to-bottom rendering.
---
## Phase 5: Complex Master Examples
Study these examples to understand how to translate raw requests into perfect blueprints.
### Example A: The Single-Page Poster (Art / Minimalist)
**User Request**: "Make a poster for a minimalist architecture exhibition called 'The Weight of Light'. Include date Oct 12, location Tokyo. Keep it very clean."
**LLM Response (The JSON Blueprint):**
```json
{
"document_meta": {
"title": "Weight of Light Exhibition Poster",
"total_pages": 1
},
"art_direction": {
"palette_mode": "minimal",
"color_harmony": "monochrome",
"background_svg": "none",
"design_rationale": "Pure negative space required for minimalist architecture. Relying entirely on typographic hierarchy and the 15% breathing rule."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "cover_hero",
"components": [
{
"type": "Floating_Meta",
"position": "top-left",
"items": ["TOKYO, JP", "EXHIBITION 04"]
},
{
"type": "Hero_Typography",
"weight": "thin",
"variant": "standard",
"content": "THE WEIGHT<br>OF LIGHT"
},
{
"type": "Hairline_Divider",
"style": "accent"
},
{
"type": "Floating_Meta",
"position": "bottom-right",
"items": ["OCTOBER 12", "AOYAMA GALLERY"]
}
]
}
]
}
```
### Example B: The Multi-Page Analytical Report (Corporate / Elegance)
**User Request**: "I have a long report about our Q3 server performance. Latency dropped by 45ms. Uptime is 99.99%. Here is 500 words of text explaining the server migration..."
**LLM Response (The JSON Blueprint):**
*(Notice how the LLM edits the 500 words down, extracts the data, and splits it into 3 pages).*
```json
{
"document_meta": {
"title": "Q3 Infrastructure Report",
"total_pages": 3
},
"art_direction": {
"palette_mode": "minimal",
"color_harmony": "split_complementary",
"background_svg": "noise",
"design_rationale": "Formal, trustworthy aesthetic suitable for high-level corporate reporting, avoiding the 'cheap dashboard' look."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "cover_hero",
"components": [
{
"type": "Floating_Meta",
"position": "top-left",
"items": ["CONFIDENTIAL", "Q3 2026"]
},
{
"type": "Hero_Typography",
"weight": "black",
"content": "INFRASTRUCTURE<br>RESILIENCE"
}
]
},
{
"page_index": 2,
"narrative_role": "Expand",
"archetype": "split_vertical",
"components": [
{
"type": "Stat_Block",
"number": "99.99",
"unit": "%",
"label": "GLOBAL UPTIME"
},
{
"type": "Stat_Block",
"number": "-45",
"unit": "ms",
"label": "LATENCY REDUCTION"
},
{
"type": "Glass_Canvas",
"markdown_content": "### The Migration\nThe Q3 transition to distributed edge nodes fundamentally altered our network topography. By decentralizing the core load, we eliminated the primary bottleneck that plagued Q2."
}
]
},
{
"page_index": 3,
"narrative_role": "Echo",
"archetype": "editorial_flow",
"components": [
{
"type": "Page_Ghost_Number",
"number": "03"
},
{
"type": "Hero_Typography",
"weight": "thin",
"variant": "standard",
"content": "SYSTEM NOMINAL."
}
]
}
]
}
```
### Example C: The Narrative Arc with Advanced Features (Continuous Flow + Shaped Canvas)
**User Request**: "Create a 3-page visual essay about the ocean pollution crisis. Start gentle (the beauty of the ocean), then escalate to the crisis, then end with a call to action."
**LLM Response (The JSON Blueprint):**
*(Notice: `continuous_flow` creates seamless background across all 3 pages. `tension_score` escalates from calm to urgent. `Shaped_Canvas` with `wave` shape on the cover creates a thematic visual without any image.)*
```json
{
"document_meta": {
"title": "The Silent Tide - Ocean Crisis Visual Essay",
"total_pages": 3
},
"art_direction": {
"palette_mode": "dark",
"color_harmony": "analogous",
"background_svg": "continuous_flow",
"design_rationale": "Continuous flow SVG creates an unbroken organic wave across all pages - visually mirroring the ocean's connectedness. Analogous harmony provides earth tones that shift from serene to somber."
},
"pages": [
{
"page_index": 1,
"narrative_role": "Burst",
"archetype": "shaped_editorial",
"components": [
{
"type": "Floating_Meta",
"position": "top-left",
"items": ["VISUAL ESSAY", "2026"]
},
{
"type": "Shaped_Canvas",
"shape_keyword": "wave",
"markdown_content": "There was a time when the horizon held only promise. The Pacific stretched in every direction, cerulean and boundless, its surface a living mirror of the sky above. Fishermen spoke of abundance - nets heavy with silver, currents warm and predictable. The ocean asked for nothing."
},
{
"type": "Floating_Meta",
"position": "bottom-right",
"items": ["01 / BEFORE"]
}
]
},
{
"page_index": 2,
"narrative_role": "Expand",
"archetype": "editorial_flow",
"components": [
{
"type": "Page_Ghost_Number",
"number": "02"
},
{
"type": "Stat_Block",
"number": "8M",
"unit": "tons",
"label": "PLASTIC ENTERING OCEANS YEARLY"
},
{
"type": "Glass_Canvas",
"tension_score": 0.75,
"markdown_content": "**The collapse was not sudden.** It accumulated - bottle by bottle, net by net, spill by spill. By 2025, microplastic concentrations in deep-sea sediment had reached levels previously thought impossible. Marine biologists stopped using the word 'recovery'. The new vocabulary was *triage*."
}
]
},
{
"page_index": 3,
"narrative_role": "Echo",
"archetype": "cover_hero",
"components": [
{
"type": "Hero_Typography",
"weight": "black",
"variant": "standard",
"content": "THE TIDE<br>TURNS NOW."
},
{
"type": "Glass_Canvas",
"tension_score": 0.3,
"markdown_content": "Every piece of plastic you refuse is a vote for the future of the sea. The ocean cannot speak - but it remembers everything we give it."
}
]
}
]
}
```
---
## Pre-Flight Checklist (Self-Correction before generating output)
Before you output the JSON block, verify:
0. ** VECTOR OUTPUT IRON RULE:** The final PDF MUST be generated via `page.pdf()` / `convert.blueprint` - NEVER `page.screenshot()` → image → wrap as PDF. Screenshot PDFs are blurry raster images. `page.pdf()` produces vector text that stays sharp at any zoom level.
1. **Did I write ANY HTML or CSS?** If yes, delete it. Only output JSON.
2. **Is any `Glass_Canvas` markdown content too long?** Count the words. Over 150? Summarize it or push to the next page.
3. **Is the JSON perfectly formatted?** Missing commas or unescaped quotes will crash the `design_engine.py` parser.
4. **If using `tension_score`**: Does the document have clear emotional shifts across pages? If all pages are the same tone, remove it.
5. **If using `continuous_flow`**: Is this multi-page (2+)? If single-page, switch to `"flow"`.
6. **If using `Shaped_Canvas`**: Is the archetype `"shaped_editorial"`? Is there at most one per page? No `Glass_Canvas` on the same page?
7. **Grid boundary check**: Are ALL `grid_area` values within `[1, 13]`? Any number < 1 or > 13 = FATAL ERROR.
8. **Component minimum size check**: `Glass_Canvas` ≥ 6col×4row? `Shaped_Canvas` ≥ 8col×6row?
9. **Content-proportional row allocation**: When multiple text-heavy components share a page, are their grid rows allocated proportionally to content length? Equal rows for unequal content = text overlap. (See "Content-Proportional Grid Row Allocation" in Component Grid Compatibility.)
9. **40% whitespace check**: Count occupied grid cells. At least 58 of 144 cells must be empty.
10. **Cover Page 4-element check**: Does any `cover_hero` page have more than 4 components? If yes, remove or merge components.
11. **Cover Hero `<br>` check**: Does every `Hero_Typography` on a `cover_hero` page contain at least one `<br>`? Single-line hero text on covers = FAIL.
12. **Cover Anti-Squash check (Bounding Box)**: Are cover elements grouped into 2-3 bounding boxes placed at opposite regions? Is remaining space dynamically distributed (not hardcoded gaps)? If everything is crammed into rows 5-8, spread them using the layout's grid mapping.
13a. **Cover Typography Scale check**: Is the hero title ≥ 45pt? Is subtitle ≥ 25pt? Is meta text ≥ 18pt (never below 14pt)? Tiny cover text = FAIL.
13b. **Cover Layout Selection**: Did I pick a layout (1-7) that matches the document tone? If unsure, default to Layout 1A (Diagonal Tension). For government/bidding documents, use Layout 7 (Left-Matrix).
14. **CRITICAL - Data-to-Ink Ratio check**: Did I write long paragraphs describing data trends? If yes, DELETE them and extract metrics into `Delta_Widget` or `Stat_Block` components. Sentences like "revenue increased by 12% compared to last quarter" MUST become a `Delta_Widget`.
15. **Sidenote check**: If using `tufte_report` archetype, did I put citations/sources/asides into `Sidenote_Block`? They must NOT be inline in `Glass_Canvas`.
16. **Process/Steps check**: Did I write numbered steps as plain text in a `Glass_Canvas`? Convert to `Process_List` component instead.
17. **Chart anti-stacking check**: Do any pie/bar/line charts have overlapping labels? Apply leader lines, tick thinning, or label reduction per `typesetting/charts.md`.
18. **Chart axis cleanup check**: Are top/right spines deleted? Grid lines dashed at 20% opacity (or hidden)? No solid grid lines.
19. **Donut default check**: Are pie charts rendered as donuts (hole ratio 60-70%)? Solid pies = FAIL unless explicitly requested.
20. ** Triple Delivery check (MANDATORY)**: Creative pipeline must deliver **three files** to the user: (1) PDF - the final vector PDF; (2) HTML - the compiled `*_rendered.html` file; (3) Image - a full-page screenshot preview (PNG/JPG). After `convert.blueprint` generates the PDF and HTML, take a screenshot of the HTML for preview. Report all three file paths to the user.
21. ** HTML Pre-Render Validation (MANDATORY for ALL HTML→PDF paths)**: Before calling `html2pdf-next.js`, `html2poster.js`, or `convert.blueprint`, run `poster_validate.py check-html` on the HTML file. This catches overflow:hidden on containers, missing @media screen auto-scale, font fallback gaps, contrast issues, and more. **Any ERROR-level issue must be fixed before generating the PDF.** Warnings are non-blocking but should be reviewed.
```bash
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html
# If errors found, auto-fix:
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html page.html --fix --output page.html
```
22. **\u26a0\ufe0f MANDATORY: Post-Generation Checks (Creative)**: After HTML\u2192PDF conversion, run these checks:
```bash
# Check for content overflow and full-bleed issues
python3 "$PDF_SKILL_DIR/scripts/pdf_qa.py" output.pdf --no-tables
# Add metadata
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.brand output.pdf
```
Execute the design.
---
## CJK & Vertical Text Rules (When Bypassing design_engine.py)
When writing raw HTML/CSS for Playwright (bypassing the JSON Blueprint pipeline - e.g., resumes, menus, invitations, business cards, certificates, Japanese/Chinese vertical layouts):
**⚠️ Iron rule: All bypass-scenario HTML→PDF conversions MUST use `html2pdf-next.js` — do NOT write custom Python Playwright scripts.** `html2pdf-next.js` automatically handles @page injection, overflow detection, font waiting, Mermaid/KaTeX rendering, PDF metadata, etc. See the "HTML→PDF Engine Selection Rules" section in SKILL.md.
```bash
node "$PDF_SKILL_DIR/scripts/html2pdf-next.js" input.html --output output.pdf --width 210mm --height 297mm
```
0. **Full-Bleed CSS (MANDATORY)**: Every HTML file for Playwright PDF MUST include:
```css
@page { size: 800px 1200px; margin: 0; } /* CONCRETE values only - CSS variables NOT supported in @page */
html, body { margin: 0; padding: 0; width: 800px; height: 1200px; }
```
**⚠️ @page rules do NOT resolve CSS variables** (`var(--x)` is silently ignored, falls back to A4). Always use concrete `px` values.
**⚠️ html/body must have explicit width + min-height** matching the canvas size. Use `min-height` (not `height`) so content taller than the design height expands naturally into a single long-page PDF.
**⚠️ Poster = seamless pagination.** When content exceeds `@page` height, `html2pdf-next.js` lets Playwright paginate at page boundaries - each page has the same dimensions, content flows seamlessly across pages (like scrolling a long image). Do NOT expand to a single oversized page.
`design_engine.py` handles this automatically via `override_css`.
0.25. **Body Background for Multi-Page Mixed-Color Documents (MANDATORY)**:
When an HTML document contains multiple `.page` divs with **different background colors** (e.g. dark cover + white body + dark specs page), Playwright's sub-pixel rounding creates <1px gaps at `.page` edges where `body` background shows through. On dark pages, a white `body` = visible white edges.
**Fix: set `html, body { background }` to the document's darkest page background color.**
```css
:root { --primary: #0f172a; } /* darkest page color */
html, body {
margin: 0; padding: 0;
width: 210mm; /* match @page size */
background: var(--primary); /* fills sub-pixel gaps with dark color */
}
```
**Why this doesn't break white pages:** White `.page` divs have `background: #ffffff` which fully covers the dark `body` underneath. The dark body only shows through the <1px sub-pixel gap at the extreme page edge - imperceptible on white pages after anti-aliasing.
**Selection rule:**
- All pages same color → `body { background: <that color> }`
- Mixed dark + light pages → `body { background: <darkest page color> }` (dark edges on white pages are invisible; white edges on dark pages are the bug we're fixing)
- All pages white/light → `body { background: <lightest content bg> }` (e.g. `#f8fafc`)
0.5. **No overflow:hidden + Browser Preview Adaptive Scaling (MANDATORY)**:
For fixed-size single-page designs (posters, infographics, certificates, etc.), **absolutely never** set `overflow: hidden` on `html`, `body`, or the main container. Reasons:
- When opening the HTML directly in a browser, the viewport is much smaller than the design size (e.g., a 1400px-tall page in a 900px viewport). `overflow: hidden` clips the bottom content and prevents scrolling.
- `html2pdf-next.js`'s pre-render check detects `scrollHeight > clientHeight` + `overflow: hidden` and triggers auto-fix (force-expanding the container), which may break the layout.
**`design_engine.py` handles this automatically**: During blueprint compilation, it auto-injects `@media screen` centering + scaling code, and the `html` background color uses `var(--c-bg)` matching the poster's main color. No manual addition needed.
**Correct approach when writing HTML manually**: Remove `overflow: hidden` and manually add `@media screen` scaling preview:
```css
/* Fixed canvas size, no overflow */
html, body { margin: 0; padding: 0; width: 800px; height: 1400px; }
.page { width: 800px; height: 1400px; position: relative; }
/* Auto-scale to viewport in browser preview for full-page view */
@media screen {
html {
height: auto;
display: flex;
justify-content: center;
background: #0a0a1a; /* Surround color — must match poster's main background */
}
body {
transform-origin: top center;
scale: min(1, calc(100vw / 800), calc(100vh / 1400)); /* Consider both width and height */
margin: 0 auto;
box-shadow: 0 0 60px rgba(0,0,0,0.3); /* Optional: shadow to distinguish canvas in preview */
}
}
```
`@media screen` rules only apply in browser preview; `page.pdf()` uses print media and is unaffected.
**Every fixed-size HTML must include this `@media screen` adaptive code.**
0.75. **Page Container Overflow Clipping (MANDATORY for multi-page documents)**:
Every `.page` div MUST have `overflow: hidden`. Decorative elements (glow circles, gradient overlays) commonly use `width: 120%` or negative offsets - without clipping, they inflate `scrollWidth` beyond page width, causing Playwright to shrink all content and shift it left.
```css
.page { overflow: hidden; } /* Clips decorative overflow, prevents Playwright shrink */
```
For horizontal flex layouts (≥3 items), always add `flex-wrap: wrap`. See `typesetting/overflow.md` §3.5.
1. **Character Encoding Safety**: Never use Japanese kana (の, が, は), rare symbols, or Private Use Area characters in content strings. They corrupt to U+FFFD (�) during LLM→file write→read transit. Replace with plain Chinese equivalents: `の`→`之/的/缔/省略`.
2. **Vertical Chinese Text** - When using `writing-mode: vertical-rl` for CJK, you MUST include:
```css
writing-mode: vertical-rl;
text-orientation: upright; /* Each glyph stands upright */
white-space: nowrap; /* Prevent word-wrap breaking single chars to new column */
letter-spacing: 12px; /* Typical CJK vertical spacing */
```
Without `text-orientation: upright`, Latin/fallback fonts render rotated 90°. Without `white-space: nowrap`, CJK characters may wrap into unexpected multi-column layouts (e.g., 3 chars on one line + 1 char alone on next).
3. **Font Coverage**: For CJK content via Playwright, always load Google Fonts Noto Serif SC or Noto Sans SC via `<link>` tag in `<head>` (NOT `@import` in CSS - `@import` must be the very first rule in a stylesheet or it's silently ignored). Example:
```html
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
```
System CJK fonts vary across macOS/Linux - Google Fonts guarantee glyph coverage without relying on system fonts. `design_engine.py` already handles this automatically via `<link>` tag.
4. **Post-Generation Text Verification**: After Playwright renders the PDF, extract text from every page and scan for `?` or `\ufffd`. If found, the source HTML has encoding-corrupted characters that must be replaced in the Python source.
5. ** HTML Pre-Render Validation (MANDATORY)**: After writing the HTML file and before running `html2pdf-next.js`, always run the HTML validator:
```bash
python3 "$PDF_SKILL_DIR/scripts/poster_validate.py" check-html <your_file>.html
```
- **ERROR** items (e.g. `OVERFLOW_HIDDEN_CONTAINER`, `FONT_NO_FALLBACK`) → must fix before PDF generation. Use `--fix --output <file>.html` for auto-repair.
- **WARNING** items (e.g. `FIXED_SIZE_NO_SCREEN_ADAPT`, `SCREEN_ADAPT_NO_SCALE`, `COLOR_CONTRAST`) → review and fix where appropriate.
- This catches the most common bypass HTML bugs: `overflow:hidden` on containers, missing `@media screen` auto-scale for fixed-size pages, font-family without generic fallback, low contrast text, etc.
---
## Quality Checklist — Creative-Specific Items
> Moved from SKILL.md to reduce context size. These rules apply specifically to the Creative pipeline.
### Color (palette.md)
- [ ] **Entire document ≤ 5 colors**: Primary + secondary + accent + neutral + background
- [ ] **All colors traceable to primary**: Secondary and accent derived via lightness/saturation/micro-hue shift
- [ ] **Sibling elements not differentiated by different hues**: Use opacity/lightness/borders instead
- [ ] **Gradient endpoints hue difference < 20°**: No warm-to-cool gradients
- [ ] **No high-saturation color blocks**: Avoid eye strain
### Geometric Anchors (geometry.md)
- [ ] **Anchors use only the primary color**: Layer via opacity, don't mix colors
- [ ] **Strokes over fills**: Solid elements ≤ 30%
- [ ] **Ultra-thin lines**: stroke-width 0.3-0.8px
- [ ] **Asymmetric placement**: Offset creates tension
- [ ] **Elements ≤ 8**: Restraint, don't clutter
### Global Layout
- [ ] **Full-bleed enforcement**: `@page { size: <w> <h>; margin: 0; }` and `html,body { margin:0; padding:0; }`
- [ ] **Background color consistency**: `html, body { background }` set explicitly. Single-color docs: match content canvas. Multi-page mixed docs: use darkest page's background
- [ ] **Content centering**: Symmetric inset/padding, no left/right drift
- [ ] **Anti-void edges**: Content fills ≥ 60% (multi-page) or ≥ 70% (single-page poster/infographic)
- [ ] **Fill Engine applied**: Pages with < 80% fill ratio trigger the fill engine
### Design Restraint (Anti-Gaudy)
- [ ] **Decorative elements ≤ 3 per page**: Cover page exempt
- [ ] **No rainbow/multi-color schemes**: Stick to single-family palette
- [ ] **No texture/pattern backgrounds**: Solid or ultra-light tinted only
- [ ] **Whitespace is design**: Don't fill every gap with decorative elements
- [ ] **Typography over decoration**: Create hierarchy through font size/weight/spacing/color
- [ ] **2-typeface maximum**: At most 2 font families
- [ ] **🚫 NO stock images / clipart / AI-generated decorations**: Only user-provided content images allowed
+702
View File
@@ -0,0 +1,702 @@
# Poster Scene Rules — Creative Pipeline (Poster-Specific Constraints)
> When poster / 海报 / 传单 / flyer / 宣传页 keywords are detected, load these rules on top of `creative-fixed-canvas.md`.
> These rules take priority over `creative-fixed-canvas.md` generic rules; in case of conflict, this file prevails.
>
> **Positioning**: This is a scene-layer patch on `creative-fixed-canvas.md` (the generic Creative pipeline) — only covers poster-specific constraints, does not repeat generic rules.
---
## 1. Page Count & Dimensions
### 1.1 Default Single Page
- When the user does not explicitly request multiple pages, **default to a single-page poster**
- Single-page poster `total_pages: 1`, never split into multiple pages on your own
### 1.2 Sizing Strategy
| Text Volume | Recommended Size | Aspect Ratio | Notes |
|--------|---------|--------|------|
| ≤ 50 chars | 720 × 960 | 3:4 | Title poster / social media cover / card |
| 50–200 chars | 720 × 960 | 3:4 | Standard promotional poster |
| > 200 chars | 720 × min-height 960 | Adaptive | Long poster (H5 style), content stretches height |
| User-specified landscape | Adjust as needed | As needed | Width and height can be swapped |
### 1.3 Canvas Variables
```json
{
"canvas": { "width": 720, "height": 960 }
}
```
Default dimensions can be overridden via the `canvas` field in the Blueprint. `design_engine.py` will automatically inject `--canvas-w` and `--canvas-h` CSS variables.
---
## 2. Information Density & Page Fill
### 2.1 Core Iron Rule: Content Must Fill the Page
> **The biggest visual disaster for a poster is not content overflow, but content only occupying half the page with a blank bottom half.**
| Text Volume | Content Area / Page Ratio | Notes |
|--------|----------------|------|
| ≤ 50 chars | 70–80% | Enlarged cards, large font sizes, generous decorative whitespace is intentional |
| 50–200 chars | 75–85% | Content modules distributed evenly, must not be crammed in the top half |
| > 200 chars | 80–90% | Content-dominant, whitespace only at margins |
### 2.2 Anti-Top-Heavy
- All components' `grid_area` **must cover the full 1→13 rows**, never stop at row 10 or 11
- The last component's `grid_area` row endpoint must be `13`
- **No large blank space at top/header** — the first content component's `grid_area` should start at row 1, not row 3 or 4
- **No large blank space on left/right sides** — components should utilize full column width (most should span 1→13 columns)
- If content is insufficient to fill 12 rows, use these strategies (by priority):
1. **Increase font size**: Hero from scale 5 → 6, body font +2pt
2. **Increase component spacing**: grid gap from 16px → 24px
3. **Insert decorative components**: `Hairline_Divider`, `Page_Ghost_Number`
4. **Expand stat block / hero grid row count**
### 2.3 Grid Area Allocation Iron Rule
```
✅ Correct: Components cover all rows 1→13
Page: [Hero 1→4] [Stats 4→7] [Glass 7→10] [Meta 10→13]
❌ Wrong: Components only reach row 10, rows 11-13 empty
Page: [Hero 1→3] [Stats 3→5] [Glass 5→8] [Meta 8→10] [??? 10→13 void]
```
### 2.4 ★★★ Content-Proportional Row Allocation (Anti-Overlap Iron Rule)
> **When two or more text components share a page, rows must be allocated proportionally to content volume — never split evenly!**
**Problem reproduction:**
```
Attractions: 450 chars (3 attractions × 150 chars description)
Food: 250 chars (2 foods × 125 chars description)
Available rows: 8 rows (5→13)
❌ Even split: Attractions 5→9, Food 9→13 → 4 rows each
→ Attractions content far exceeds 4-row capacity, overflows into Food area, text overlaps!
✅ Proportional split:
Attractions: 8 × 450/700 ≈ 5 rows → 5→10
Food: 8 × 250/700 ≈ 3 rows → 10→13
```
**Steps:**
1. Write all `markdown_content` first
2. Count the **character count** of each text component (including titles/paragraphs/lists)
3. Allocate rows proportionally: `rows_i = total_rows × (chars_i / total_chars)`
4. Round off, each component **minimum 3 rows**
5. Verify: all component grid_area endpoints connect end-to-end, covering full 1→13
**Applicable scenarios:**
- Multiple `Glass_Canvas` on the same page (most common)
- `Glass_Canvas` + `Process_List` on the same page
- Any two or more components containing text paragraphs
**Not applicable:**
- `Hero_Typography` (very large font, 1-3 lines occupying 2-3 grid rows is reasonable)
- `Stat_Block` (number + label, fixed height, typically 2 rows)
- `Floating_Meta` (short labels, typically 2-3 rows)
---
## 3. Font Size System (Poster-Specific)
### 3.1 Minimum Font Size (Hard Floor)
| Element | Min Font Size | Recommended | Notes |
|------|---------|---------|------|
| **Page main title** | **50px** | 56–72px | Poster title must have visual impact |
| **Body text** | **24px** | 24–28px | Posters are not reports — body font must be large |
| **Subtitle / card title** | **28px** | 32–40px | Secondary headings |
| **Floating Meta** | **16px** | 16–20px | Metadata text |
| **Stat Number** | **48px** | 56–72px | Data sculptures must be eye-catching |
| **Stat Label** | **14px** | 14–16px | Data labels |
> Compared to `fill-engine.md`'s generic red line (body ≥ 14pt), the poster body floor is **24px** — posters are a distance-reading medium, font sizes must be larger.
### 3.2 Emphasis Hierarchy
- Use **font size + font weight** to create hierarchy, **not color differentiation**
- Emphasis text (keyword/number highlights) font size must be smaller than titles
- Hero title recommended `weight: "black"` (900), subtitle `weight: "thin"` (100), creating extreme contrast
---
## 4. Color Rules (Poster-Specific)
### 4.1 Color Palette System
**Primary palette: Material Design 3 with low-medium saturation.** Default to medium-saturation colors; for light themes, use gradient backgrounds with white/light text on top.
| Area | Proportion | Role |
|------|------|------|
| 60% Ground | Background/margins/whitespace | Main color (low saturation) |
| 30% Structure | Cards/dividers/secondary areas | Derived from main by adjusting lightness |
| 10% Emphasis | Titles/key numbers/single accent | Adjusted purity/brightness of main color |
**Color derivation rules:**
- Title/subtitle text color: Adjust main color's purity and brightness (not a separate random color)
- Auxiliary colors: **Maximum 2**, derived from primary by adjusting lightness/saturation
- Keep consistent color palette throughout — do NOT change main color between sections
- Default recommendation: **Light theme with medium saturation**, or gradient background + white fonts
### 4.2 Poster Additional Constraints
- **No pure white (#FFFFFF) background** — use at least `#f5f4f2` or warmer off-white
- **No transparent background**
- **Page base color (`<body>` / `<html>` background) must match poster content background** — `printBackground: true` renders body background. If body is white but poster content is gray, white borders/gaps appear in the PDF. Ensure `html, body { background: var(--c-bg); }` matches the poster canvas exactly
- **Do not use a single image to fill the background** — use grid textures, gradients, geometric shapes, and other generative backgrounds
- Long posters (>200 chars) must not use full-image backgrounds
- **Gradients** or **large blurred circles/symbols** can be used sparingly as background accents
- Maximum 2 auxiliary colors, derived from primary by adjusting lightness/saturation
- Background accents: grid textures, organic shapes, large blurred circles/symbols — NOT a single image
- **Overall bright and vibrant color combinations** — the poster should feel visually striking, not muted/dull
### 4.3 palette_mode Mapping
| Poster Style | Recommended palette_mode | color_harmony |
|---------|-------------------|---------------|
| Business/Formal | `minimal` | `auto` |
| Tech/AI | `dark` | `complementary` |
| Lifestyle/Food/Artistic | `pastel` | `analogous` |
| Luxury/Ceremony | `jewel` | `split_complementary` |
| Other | `minimal` (default) | `auto` |
---
## 4.5 ★★★ Anti-Modularization Iron Rule (Anti-Card / Anti-Dashboard)
> **A poster is a complete composition, not a dashboard, not an APP interface, not a report.**
### Root Cause
LLMs naturally tend to "classify information → put each category in a box → stack them vertically", because that is report/document organization logic. But poster visual logic is the exact opposite — **information is unified, hierarchy is established through typographic rhythm (font size, weight, spacing, whitespace), not through borders and background colors**.
### Comparison
| Report/UI Thinking ❌ | Poster Thinking ✅ |
|---|---|
| Each info block wrapped in `border + border-radius + background` as a card | Information placed directly on the canvas, no borders no background |
| Clear visual boundaries between modules | Modules naturally separated by **whitespace and thin lines** |
| Looks like a mobile APP interface | Looks like a design piece |
| Hierarchy via "different colored boxes" | Hierarchy via **font size gradient + weight contrast + color lightness** |
| Stacked Glass_Canvas components = card wall | Pure typography + occasional Hairline_Divider |
### Mandatory Rules
1. **Single-page posters must not have more than 3 containers with `background` + `border`.** If information needs grouping, use whitespace (margin/padding) and thin lines (1px, opacity < 10%) instead of bordered cards.
2. **No 2×2 / 2×3 grid card layouts** (unless it's a pure data scenario with `data_dashboard` archetype). Information flows in a single column; multiple items in the same row use `flex + gap` horizontal layout without borders.
3. **Information hierarchy must be established through typography properties**, not through "different colored/backgrounded boxes":
- Primary information: large font (≥48px) + heavy weight (900)
- Secondary information: medium font (18-24px) + medium weight (700)
- Tertiary information: small font (12-14px) + light weight (300-400) + reduced opacity
4. **Glass_Canvas may be used at most once in a single-page poster.** If multiple text sections are needed, use direct HTML typography instead of wrapping each paragraph in a Glass_Canvas.
5. **Bottom action information (price/address/QR code) should not be wrapped in a separate color block.** Use larger font + heavier weight to emphasize, keeping it unified with the overall composition.
### Correct Example (Direct HTML Flow)
```html
<!-- ❌ Wrong: card wall -->
<div class="card" style="background:rgba(255,255,255,0.5); border-radius:12px; border:1px solid #ddd;">
<h3>📅 展期</h3>
<p>7.15 — 8.30</p>
</div>
<div class="card" style="background:rgba(255,255,255,0.5); border-radius:12px; border:1px solid #ddd;">
<h3>📍 地点</h3>
<p>城市艺术中心</p>
</div>
<!-- ✅ Correct: pure typography, no borders -->
<div class="info-row" style="display:flex; gap:48px;">
<div>
<div style="font-size:10px; letter-spacing:3px; opacity:0.45;">展期</div>
<div style="font-size:20px; font-weight:700;">7.15 — 8.30</div>
</div>
<div>
<div style="font-size:10px; letter-spacing:3px; opacity:0.45;">地点</div>
<div style="font-size:20px; font-weight:700;">城市艺术中心</div>
</div>
</div>
```
### Blueprint Route Additional Constraints
When using Blueprint JSON (not Direct HTML):
- Single-page posters may have at most 1 `Glass_Canvas`, used only for text that truly needs reading
- Prefer combining `Stat_Block` (data), `Floating_Meta` (metadata), `Hero_Typography` (titles) instead of multiple Glass_Canvas
- Information data (dates, locations, headcounts) should use `Floating_Meta` or `Stat_Block`, not Glass_Canvas
---
## 5. Layout Strategy
### 5.1 Composition Priority
1. **Vertical composition** (vertical flow) — most common, information flows top to bottom
2. **Left-right composition** (split vertical) — image and text split, `archetype: "split_vertical"`
3. **Centered composition** (centered) — suitable for title cards with ≤ 50 chars
### 5.2 Card Rules
- Cards **must never overlap**
- Card content should be well-distributed, **no large internal whitespace**
- Text within cards **vertically centered**
- Glass Canvas `align-items` must be `stretch` (built into the engine)
### 5.3 Breathing Margins
| Scenario | safe-zone inset | Notes |
|------|----------------|------|
| Cover / title poster (≤50 chars) | `12% 14%` | Generous whitespace is intentional |
| Standard poster (50-200 chars) | `8% 10%` | Balance whitespace and content |
| Long poster (>200 chars) | `6% 8%` | Maximize content area |
> Max content width = 80% of page width (consistent with original prompt)
---
## 5.5 Visual Impact Rules
### 5.5.1 Emphasis & Contrast
- Create visual contrast with **oversized and small elements** — hero numbers/titles vs tiny metadata
- Highlight core points with large fonts or numbers for strong visual contrast
- **Emphasized text must remain smaller than headings/titles** — never let a highlight be bigger than the heading
- Texts that need emphasis can be highlighted with color, weight, or circled with hand-drawn lines
- **Do not insert multiple small pictures as embellishments** — this won’t enhance visual appeal
### 5.5.2 Alignment & Spacing
- Allow blocks to resize based on content, align appropriately, optimize space utilization
- If excess whitespace exists, **enlarge fonts or modules** to balance the layout
- Cards cannot overlap; content should fill the card area without excessive empty space
- Use **flexbox** layouts to prevent footer from moving up (with top and bottom margin settings)
- For visual variety: encourage diverse and creative layouts beyond standard grids, while maintaining alignment and hierarchy
---
## 6. Design Style Library
Based on content theme, the model should autonomously select one of the following styles. Explain the selection rationale in the Blueprint's `design_rationale`.
| Style | Characteristics | Applicable Scenarios |
|------|------|---------|
| **Modern Minimal** | Clean colors, organic shapes, flowing curves, rounded cards, clear hierarchy | Business, tech, education |
| **Neo-Brutalism** | Flat elements, illustrations, patterns, large text blocks, special font designs, thick borders | Creative, events, youth |
| **Artistic Gradient** | Diffused light, gradient glow, semi-transparent elements, blur effects, glass texture | Art, music, branding |
| **Collage** | Contrasting color design, material collage, large text, irregular layout | Trends, fashion, exhibitions |
| **Playful UI** | Bright colors, interesting shapes, energetic | Children, games, social |
### Special Forms for Text Volume ≤ 50 Characters
When content is minimal, prioritize the following compact forms:
| Form | Description | archetype |
|------|------|-----------|
| **Centered Card** | Calendar-like effect, key content centered as note/card | `cover_hero` |
| **Bookmark Page** | Narrow tall ratio (e.g. 360×960), vertical reading | `cover_hero` |
| **Minimal Text** | Title + whitespace only, no additional information | `cover_hero` |
| **Sticky Note** | Content displayed as floating sticky notes above background | `cover_hero` |
| **Polaroid Card** | Photo-style card with caption below | `cover_hero` |
### Special Forms for Text Volume ≤ 100 Characters
| Form | Description |
|------|------|
| **Floating Card System** | Content as cards floating above organic shape backgrounds |
| **Notebook Style** | Lined-paper aesthetic with handwritten-feel content |
| **Fortune Stick** | Vertical strip with centered calligraphy text |
> **Key constraint**: If the user only provides a title with no other requirements, **do not expand content on your own** — just place the title.
---
## 6.5 Icons and Illustrations
- Use **Material Design Icons** (Google Fonts method):
```html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Usage: -->
<i class="material-icons">icon_name</i>
```
- Icon color: Use the **theme color** (not random colors)
- Icon size and position: Aligned with surrounding elements, never stretched
- If positioned beside text: **center-aligned with the first line of text**
- **Emoji can be used as icons** — 🌸 🍴 🏙️ etc. (but remember ReportLab can’t render emoji — only use in HTML/Playwright route)
- For logos/emblems: Use text "Your Logo" or icons, **never** search for logo images
---
## 7. Text Readability
### 7.1 Iron Rules
- **Line height ≥ 130%** (`line-height: 1.3` or above)
- **No text shadow/glow effects**
- **When text overlays images**, a semi-transparent mask layer is required
- **Do not use images with lots of text/charts/numbers as text background**
- **No `text-align: justify`** (CJK characters get stretched letter-spacing, terrible result) — always use `text-align: left`
### 7.2 Contrast
- Text on light background: `color: #242220` or darker
- Text on dark background: `color: #f5f4f2` or lighter
- Text on medium background (L 0.30–0.70): **forbidden** — do not place text on mid-tone backgrounds
---
## 8. Image Usage Rules
### 8.0 Image Recommendations
When creating posters, actively use images to enrich visual effects. Good images can significantly enhance the poster's visual impact.
- Priority: **installed image generation skill** > web image search
- Image style must match the poster theme
- Download images locally first, then embed into the poster
- Local images must be converted to base64 data URI in HTML (Playwright cannot load local absolute paths)
### 8.1 Core Principles
- Don't force images when none are suitable, but images improve results when available
- Each image must be **unique** in the design, no reuse
- Prefer clear, high-resolution, watermark-free, text-free images
- Images should have rounded corners, sized consistently with the overall design
- You can try adding **irregularly shaped masks** (CSS `clip-path`) to images for visual interest
### 8.2 Prohibited Behaviors
- ❌ Placing images directly in corners
- ❌ Images obscuring text or overlapping with other modules
- ❌ Multiple images scattered randomly as decoration
- ❌ Searching for images when logo/badge is needed — use text "Your Logo" or icons instead
---
## 9. Chart Rules (Poster Scene)
- Large numerical datasets → consider creating visual charts
- Chart style should match the poster theme
- Use **Bento Grid** layout for multiple charts
- Chart containers **must have height constraints** to prevent infinite growth
---
## 10. Prohibited Items (Poster-Specific)
### 10.0 HTML Rendering Iron Rules (Applicable to All HTML → PDF Routes)
| Rule | Description |
|------|------|
| **No `overflow: hidden` on content components** | Truncates text. Only allowed on page-level `.canvas` and decorative background layers |
| **Use `min-height` instead of `height` for content containers** | `height:100%` locks height, content gets clipped when too much; `min-height:100%` allows natural expansion |
| **Exceptions where `overflow: hidden` is allowed** | `.canvas` (page boundary), `.poster` (auto-injected by `html2poster.js`), `.floating-meta` (short label ellipsis), cover Layer 1 (decorative clipping), SVG/img (fill container) |
| **Absolutely no `backdrop-filter`** | **Playwright PDF rendering silently discards entire element content!** Use fixed rgba() background color for cards instead |
| **Absolutely no `text-align: justify`** | CJK character spacing gets abnormally stretched, always use `text-align: left` |
| **`overflow: hidden` on `.page` containers** | **MANDATORY for multi-page documents.** Decorative elements (glow, gradient circles, oversized backgrounds) with `width > 100%` or negative offsets cause `scrollWidth > clientWidth`, triggering Playwright to shrink the entire page → content drifts left. `.page { overflow: hidden }` clips decorative overflow without affecting visible content |
| **Horizontal flex rows must have `flex-wrap`** | ≥3 inline items (flow bars, step lists, tag rows) without `flex-wrap: wrap` will overflow the page right edge when content is long. See `typesetting/overflow.md` §3.5 for full rules |
| Prohibited | Reason |
|--------|------|
| ❌ Timeline graphics | Complex connecting lines easily misalign in PDF rendering |
| ❌ Complex SVG-drawn structure/flow diagrams | Unless user explicitly requests |
| ❌ Code-drawn maps or flags | Poor quality |
| ❌ Base64 images (when exceeding 10MB) | File too large. Small image base64 is acceptable (Playwright cannot load local paths) |
| ❌ Content truncation | Must adjust container height to ensure all content fully displayed |
| ❌ Pure white background (#FFFFFF) | Lacks design quality |
| ❌ Transparent background | PDF output cannot be transparent |
---
## 11. Poster Font Recommendations
### 11.1 CJK Font Recommendations
**For Chinese posters (serious/formal scenes):**
| Purpose | Recommended Font | Google Fonts / CDN Name | Style |
|------|---------|-------------------|------|
| CJK title (serious) | DingTalk JinBuTi | `DingTalk JinBuTi` (letter-spacing: -5%) | Bold, impactful |
| CJK title (alt) | Douyin Sans / Alimama FangYuanTi VF Bold | Via CDN | Modern Chinese |
| CJK title (serif) | Swei B2 Serif CJKtc Bold | Via CDN | Elegant serif |
| CJK body | HarmonyOS Sans SC | `HarmonyOS Sans SC` | Clear, readable |
| CJK body (fallback) | Noto Sans SC Regular | `Noto Sans SC:wght@400` | Google Fonts guaranteed |
| CJK artistic | Noto Serif SC Bold | `Noto Serif SC:wght@700` | Elegant, artistic |
| Handwritten | ZhanKuKuaiLeTi2016XiuDingBan-2 | Via CDN | Casual, playful |
| Pixel/dot-matrix | DottedSongtiSquareRegular | Via CDN | Retro, xiangsuti |
**For English posters:**
| Purpose | Recommended Font | Google Fonts Name | Style |
|------|---------|-------------------|------|
| English/number title | Futura | System / `Futura` | Classic geometric |
| English body | Noto Sans SC | `Noto Sans SC:wght@400` | Clean, modern |
| English title (Google) | Inter Black | `Inter:wght@900` | Modern geometric |
| English serif | Playfair Display | `Playfair+Display:wght@900` | Classic editorial |
| Number emphasis | Inter Black | `Inter:wght@900` | Data sculpture |
### 11.2 Font Usage Constraints
- Entire poster **maximum 3 fonts**
- Title and body may use different fonts, but must be visually harmonious
- **Never reduce font size or line height to squeeze in more content**
- Font content in cards should be **vertically centered** within the card
- You may use different style fonts for entertaining or artistic scenes
---
## 12. Coordination with design_engine.py
### 12.0 ★★★ Stable Layout Selection Strategy (Anti-Overflow Core Rule)
> **Content-heavy posters must bypass the Blueprint 12×12 Grid and use a pure flow-based HTML approach.**
**Why:** The Blueprint 12×12 Grid allocates space with fixed row heights and cannot predict actual text rendering height, causing:
- Text overflowing Glass Canvas containers
- CJK character spacing stretched by `text-align: justify`
- Inaccurate row allocation for multiple components, text overlap
**Route selection:**
| Scenario | Recommended Route | Notes |
|------|---------|------|
| Title poster (≤ 50 chars) | Blueprint JSON | Few components, little content, Grid sufficient |
| Standard poster (50-150 chars) | Blueprint JSON | Grid mostly sufficient, mind row allocation |
| **Info-dense poster (>150 chars or multiple sections)** | **★ Direct HTML Flow** | **Strongly recommended — completely avoids overflow** |
| **Multi-page info poster** | **★ Direct HTML Flow** | **Strongly recommended** |
| Poster with images | Direct HTML Flow | Image embedding is more stable |
### 12.1 Direct HTML Flow Approach (Recommended Default)
**Core idea: No Grid, no fixed height, content flows naturally, never overflows.**
Write HTML directly, convert to PDF via `html2poster.js`. The poster is a **single continuous `<div class="poster">` container** — NOT split into separate `<div class="page">` blocks.
#### ★★★ Single-Canvas vs Multi-Page (Iron Rule)
| Approach | When to Use | HTML Structure |
|----------|-------------|----------------|
| **Single Canvas** (default) | All content forms one unified poster | One `<div class="poster">`, no `page-break` |
| **Multi-Page** (exception) | User explicitly requests separate pages (e.g., "make a 4-page booklet") | Multiple `<div class="page">` with `page-break-after: always` |
> **Default = Single Canvas.** A poster is ONE design composition, not a paginated report. Multi-page is only for booklets/multi-page documents where the user explicitly asks for page separation.
#### ★★★ Dynamic Height (Anti-Blank-Bottom Iron Rule)
**NEVER hardcode `@page { size: W H }` or `.poster { min-height: Xpx }` with a fixed height.** This creates blank space at the bottom when content is shorter than the hardcoded value.
**Correct approach — use `html2poster.js`:**
`html2poster.js` automatically handles all of this — you just need to write the HTML correctly and call one command:
```bash
node "$PDF_SKILL_DIR/scripts/html2poster.js" poster.html --output poster.pdf --width 720px
```
It will automatically:
1. Add `overflow: hidden` to `.poster` container (clips decorative overflow)
2. Inject `@page { margin: 0 }` (zero margins)
3. Sync `html/body` background with `.poster` background
4. Measure `.poster` scrollHeight (actual content height)
5. Generate single-page vector PDF with exact dimensions
**⚠️ Do NOT use `html2pdf-next.js` for posters.** It is designed for multi-page documents and will inject 20mm margins / A4 pagination.
**⚠️ Do NOT write hand-written Playwright scripts for posters.** `html2poster.js` handles everything.
```css
/* ✅ CORRECT CSS for poster HTML (html2poster.js handles the rest): */
html, body { margin: 0; padding: 0; background: var(--c-bg); }
.poster { width: 720px; position: relative; background: var(--c-bg); }
/* Note: overflow:hidden on .poster is auto-injected by html2poster.js,
but including it in CSS is fine too */
```
**CSS iron rules:**
```css
html, body { margin: 0; padding: 0; background: var(--c-bg); }
/* Single poster canvas — NO fixed height */
.poster {
width: 720px;
position: relative;
overflow: hidden;
background: var(--c-bg);
/* Height is determined by content, measured at render time */
}
/* Card/content block */
.card {
background: rgba(255,255,255,0.7);
border-radius: 12px;
padding: 24px 28px;
margin-bottom: 20px;
/* No height, no max-height, no overflow:hidden */
}
/* CJK text iron rules */
.card, .card p, .card li {
text-align: left; /* Absolutely no justify! CJK stretches letter-spacing */
line-height: 1.6;
word-break: break-all; /* CJK natural line break */
}
/* Image */
.hero-image {
width: 100%;
border-radius: 8px;
margin-bottom: 20px;
display: block;
}
```
**HTML structure template (Single Canvas):**
```html
<div class="poster">
<!-- Hero section -->
<div class="hero"> ... </div>
<!-- City 1 -->
<div class="city-section">
<h2>南京</h2>
<div class="items-row"> ... attractions ... </div>
<div class="food-row"> ... food ... </div>
</div>
<!-- City separator (thin line, NOT page-break) -->
<div class="city-sep"></div>
<!-- City 2 -->
<div class="city-section"> ... </div>
<!-- Footer -->
<div class="poster-footer"> ... </div>
</div>
```
**Why it's stable:**
- No fixed height — content naturally defines poster size
- No `overflow: hidden`, text always fully displayed
- `text-align: left` avoids CJK letter-spacing stretch
- Single canvas = one unified composition, not chopped pages
- PDF height measured at render time = zero blank space
**Convert to PDF and PNG:**
```bash
# PDF (vector, single-page, zero margins, auto-height):
node "$PDF_SKILL_DIR/scripts/html2poster.js" poster.html --output poster.pdf --width 720px
# PNG preview (screenshot):
# Use Playwright screenshot with measured height
```
> **⚠️ Do NOT write hand-written Playwright `page.pdf()` scripts.** Use `html2poster.js` which handles overflow:hidden, margin:0, background sync, and height measurement automatically.
### 12.2 Blueprint Grid Approach (Only for Simple Posters)
| Behavior | Status | Notes |
|------|------|------|
| Dynamic inset | ✅ Fixed | More content → smaller inset (`8% 10%`), less content → default (`10% 12%`) |
| Glass Canvas overflow | ✅ Fixed | `min-height:100%` replaces `height:100%`, removed `overflow:hidden` |
| Glass Canvas / Process List stretch | ✅ Fixed | Auto `align-items: stretch` |
### 12.3 Poster Marker in Blueprint
Add `scene: "poster"` marker in `art_direction` so `design_engine.py` can identify poster scenes and apply specific logic in the future:
```json
{
"art_direction": {
"scene": "poster",
"palette_mode": "minimal",
"color_harmony": "auto",
"background_svg": "flow",
"design_rationale": "..."
}
}
```
---
---
## 14. PDF Conversion Iron Rules
### 14.1 Background Color Consistency
```css
/* Must ensure html/body background = poster canvas background */
html, body {
background: var(--c-bg); /* Same color as .canvas background */
}
```
- Playwright `page.pdf({ printBackground: true })` renders body background color
- If body is white but poster is gray, white borders appear in the PDF
- `design_engine.py` already auto-injects `background: var(--c-bg)`, but if bypassing the engine and writing HTML directly, **you must ensure manually**
**Multi-page posters / brochures with mixed page backgrounds:**
- When pages alternate between dark and light backgrounds, set `body { background }` to the **darkest page color** (see SKILL.md "Background Color Consistency" for full rationale)
- This eliminates sub-pixel white edges on dark pages without affecting light pages
### 14.2 Content Centering (Anti-Drift)
**Poster content must be centered in the PDF, no left or right drift allowed.**
Common drift causes and fixes:
| Cause | Fix |
|------|------|
| `@page { margin }` not 0 | Must be `@page { size: <w> <h>; margin: 0; }` |
| `.safe-zone` `inset` left-right asymmetric | Ensure `inset: Y% X%` uses same X% for left and right |
| Component `grid_area` only uses partial columns | Most components should span `1 / 1 / X / 13` (full width) |
| Content container has `max-width` but no `margin: 0 auto` | Add `margin: 0 auto` to center |
| Playwright PDF default margin | Pass `margin: { top: 0, right: 0, bottom: 0, left: 0 }` |
### 14.3 Anti-Blank Edges (Dynamic Height Iron Rule)
**Poster edges, top, and bottom should not have large meaningless whitespace.**
**★★★ CRITICAL: Never hardcode poster height.** The poster is a single continuous canvas — its height is defined by content, not by a fixed CSS value.
| Pattern | Status | Result |
|---------|--------|--------|
| `@page { size: 720px 3600px }` | ❌ FORBIDDEN | Creates 853px+ blank space at bottom if content is shorter |
| `.poster { min-height: 3600px }` | ❌ FORBIDDEN | Same problem — blank bottom |
| `.poster { width: 720px }` (no height) | ✅ CORRECT | Content defines height naturally |
| `node html2poster.js poster.html --width 720px` | ✅ CORRECT | Auto-measures height, zero blank space |
**The 720×960 dimension is for multi-page documents with `page-break-after: always` only — NOT for single-canvas posters.**
- Content must make full use of page area, no more than 20% unused space within safe-zone
- If excess whitespace exists, enlarge font sizes or modules to balance the layout
- Checklist:
- [ ] Poster height defined by content (no hardcoded height)?
- [ ] PDF generated via `html2poster.js` (not html2pdf-next.js)?
- [ ] First component starts from the top?
- [ ] Last component reaches the bottom with proper padding?
- [ ] Left-right margins symmetric?
- [ ] No blank space at bottom of PDF?
---
## 15. Preflight Checklist (Poster-Specific)
Before outputting JSON Blueprint, verify the following items:
```
□ ★ Single-canvas check: poster is ONE continuous <div>, not split into separate pages? (unless user explicitly requests multi-page)
□ ★ Dynamic height check: no hardcoded @page size height or .poster min-height? PDF generated via html2poster.js?
□ ★ Anti-modularization check: ≤ 3 containers with background+border in single-page poster? No 2×2 card grid? Hierarchy via font size/weight not border colors?
□ Emphasis elements (price/date/address) highlighted via font size+weight, not wrapped in separate color blocks?
□ Overall looks like a design piece, not an APP interface or dashboard?
□ total_pages = 1 (single canvas)? (unless user explicitly requests multi-page booklet)
□ No hardcoded @page height or .poster min-height? Content defines height?
□ Left-right margins symmetric? (no left/right drift)
□ html/body background = poster canvas background? (no color mismatch)
□ No bottom blank space in PDF? (height measured dynamically)
□ Page main title ≥ 50px? Body text ≥ 24px?
□ Text volume ≤ 150 chars? (single Glass Canvas)
□ palette_mode not #FFFFFF pure white?
□ No single image filling the background?
□ No overlap between cards?
□ No Timeline / complex SVG structure diagrams?
□ All content fully displayed, not truncated?
□ Emphasis text font size < title font size?
□ Entire design ≤ 3 fonts?
□ cover_hero page ≤ 4 components?
□ Hero_Typography has <br> line breaks?
□ @page { margin: 0 } set? (prevents PDF drift)
```
+433
View File
@@ -0,0 +1,433 @@
# Brief: PDF Processing
Work with existing PDFs: extract, merge, split, fill forms, convert formats, or **reformat** with a new design. Usually a Light triage path — except reformat, which escalates to Standard.
---
## Decision Tree
```
User request
├─ "Extract text/tables/images" → §Extract
│ └─ No text extracted? → §OCR Fallback
├─ "Merge/split/rotate/crop pages" → §Pages
├─ "Fill a form" → §Forms (check fillable first)
├─ "Read/write metadata" → §Metadata
├─ "Convert DOCX/PPTX/XLSX to PDF" → §Convert
│ └─ DOCX with TOC? → §DOCX Pipeline (5-step)
├─ "Redesign/reformat a document" → §Reformat
│ └─ With a reference template? → §Template-Guided Reformat
├─ "Decrypt/repair a PDF" → §Encrypted / §Corrupted
├─ "Optimize file size" → §Optimize
└─ "Batch process many PDFs" → §Batch Processing
```
---
## Environment Check
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" env.check
```
Reports availability but does **not** auto-install.
Entry point: `python3 "$PDF_SKILL_DIR/scripts/pdf.py" <group>.<action> [options]`
All commands return JSON on stdout (`{"status": "success", "data": {...}}`) or stderr (`{"status": "error", ...}`).
Exit codes: 0 = success, 1 = bad args, 2 = file not found, 3 = parse error, 4 = operation failed.
---
## §Extract
### Basic Extraction (pdf.py)
```bash
# Text (full or page range)
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text report.pdf
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text report.pdf -p 1-3
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.text report.pdf -p 1,4,7
# Tables — returns structured JSON
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.table report.pdf
# Images — dumps embedded rasters to directory
python3 "$PDF_SKILL_DIR/scripts/pdf.py" extract.image report.pdf -o ./images/
```
### Precise Coordinates (pdfplumber)
```python
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
page = pdf.pages[0]
# Characters with exact coordinates
for char in page.chars[:10]:
print(f"'{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
# Text within a bounding box (left, top, right, bottom)
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
# Advanced table extraction with custom settings
table_settings = {
"vertical_strategy": "lines",
"horizontal_strategy": "lines",
"snap_tolerance": 3,
"intersection_tolerance": 15
}
tables = page.extract_tables(table_settings)
# Visual debugging
img = page.to_image(resolution=150)
img.save("debug_layout.png")
```
### Fast Rendering (pypdfium2)
pypdfium2 is a Python binding for PDFium (Chromium's PDF library) — fast rendering and text extraction.
```python
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument("document.pdf")
# Render single page
bitmap = pdf[0].render(scale=2.0)
bitmap.to_pil().save("page_1.png", "PNG")
# Batch render all pages
for i, page in enumerate(pdf):
bitmap = page.render(scale=1.5)
bitmap.to_pil().save(f"page_{i+1}.jpg", "JPEG", quality=90)
# Extract text
for i, page in enumerate(pdf):
print(f"Page {i+1}: {len(page.get_text())} chars")
```
### CLI Rendering & Extraction (poppler-utils)
```bash
# High-resolution PNG rendering
pdftoppm -png -r 300 document.pdf output_prefix
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res # specific pages
# Text with bounding box coordinates
pdftotext -bbox-layout document.pdf output.xml
# Extract embedded images (faster than full-page rendering)
pdfimages -all document.pdf images/img
pdfimages -list document.pdf
```
### §OCR Fallback (Scanned PDFs)
When `pdfplumber` extracts 0 characters, the PDF is likely a scanned image:
```python
import pytesseract
from pdf2image import convert_from_path
def extract_text_with_ocr(pdf_path):
images = convert_from_path(pdf_path)
return "".join(pytesseract.image_to_string(img) for img in images)
```
Prerequisites: `pip install pdf2image pytesseract` + install Tesseract OCR and poppler.
---
## §Pages
### Basic Operations (pdf.py)
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.merge a.pdf b.pdf -o combined.pdf
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.split book.pdf -o ./chapters/
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.rotate doc.pdf 90 -o rotated.pdf
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.rotate doc.pdf 180 -o rotated.pdf -p 1-3
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.crop doc.pdf 50,50,550,750 -o trimmed.pdf
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean doc.pdf -o cleaned.pdf
```
### Complex Page Manipulation (qpdf)
```bash
# Split into groups of N pages
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
# Extract complex page ranges
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
# Merge specific pages from multiple PDFs
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
```
---
## §Metadata
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.get doc.pdf
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.set doc.pdf -o out.pdf -d '{"Title": "Report", "Author": "Jane"}'
python3 "$PDF_SKILL_DIR/scripts/pdf.py" meta.brand doc.pdf -o branded.pdf
```
Recognised keys: `Title`, `Author`, `Subject`, `Keywords`, `Creator`, `Producer`.
---
## §Forms
### Step 1 — Check if fillable
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.info input.pdf
```
If `has_fields: true` → **Fillable workflow**. If `false` → **Non-fillable workflow**.
### Fillable Workflow
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.fill input.pdf -o filled.pdf \
-d '{"name": "John", "agree": "true", "country": "US"}'
```
| Type | Value | Example |
|------|-------|---------|
| text | Free string | `"name": "Jane Doe"` |
| checkbox | `"true"` / `"false"` | `"agree": "true"` |
| radio | One of `radio_options[].value` | `"gender": "/Choice1"` |
| dropdown | One of `choice_options[].value` | `"country": "US"` |
For complex forms: `form.detail input.pdf -o fields.json` (full field info) and `form.render input.pdf -o ./pages/` (visual check).
### Non-Fillable Workflow (Annotation-Based)
For PDFs without interactive fields (scanned forms, image-based). All four steps are mandatory.
**Step 1 — Render pages as PNG**:
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.render input.pdf -o ./pages/
```
**Step 2 — Create `fields.json`** with annotation regions. Determine bbox coordinates by inspecting the rendered PNG (use PIL to get pixel dimensions, then estimate [left, top, right, bottom] for each field). `dims` must match PNG dimensions exactly.
```json
{
"sheet": [{
"pg": 1, "dims": [1000, 1400],
"regions": [{
"id": "last_name", "hint": "Last name field",
"label": {"tag": "Last name", "bbox": [30, 125, 95, 142]},
"target": {"bbox": [100, 125, 280, 142]},
"ink": {"value": "Simpson", "size": 14, "color": "000000"}
}]
}]
}
```
**Step 3 — Validate**:
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.check-bbox fields.json
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.validate 1 fields.json page1.png validation.png
```
**Step 4 — Fill**:
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" form.annotate input.pdf fields.json -o filled.pdf
```
---
## §Encrypted
```bash
# Check encryption status
qpdf --show-encryption encrypted.pdf
# Decrypt with password
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
# Encrypt a PDF
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
```
Python alternative:
```python
from pypdf import PdfReader
reader = PdfReader("encrypted.pdf")
if reader.is_encrypted:
reader.decrypt("password")
```
---
## §Corrupted
```bash
qpdf --check corrupted.pdf
qpdf --replace-input corrupted.pdf # in-place repair
```
---
## §Optimize
```bash
qpdf --linearize input.pdf optimized.pdf # web-optimized (fast first-page load)
qpdf --optimize-level=all input.pdf compressed.pdf # maximum compression
```
---
## §Reformat
Take an existing document and rebuild it with a new visual design. Content is preserved; layout is rebuilt.
```
1. EXTRACT → Extract content from source
2. STRUCTURE → Organize into sections (headings, body, tables, lists)
3. DELEGATE → Route to appropriate brief:
Structured → briefs/report.md (ReportLab)
Visual → briefs/creative-fixed-canvas.md (Playwright)
4. BUILD → Follow the delegated brief's full workflow
5. DELIVER → New PDF, same content, new design
```
### §Template-Guided Reformat
When user provides a reference PDF to match:
```
1. ANALYZE → Extract design DNA from template:
- meta.get (page size), extract.image (color samples),
extract.text (structure), pdftoppm (visual reference)
2. DOCUMENT → Record: page size, margins, colors, fonts, layout, header/footer
3. DELEGATE → Route to brief WITH design constraints (not defaults)
4. BUILD → Follow brief workflow, constrained to template DNA
5. COMPARE → pdftoppm both, visually compare side-by-side
```
**Key principles:** Match the spirit, not the pixels. Prefer source files over PDF when available. Declare font substitutions upfront.
---
## §Convert
### Office → PDF (LibreOffice)
```bash
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office input.docx -o output.pdf
# Or directly:
soffice --headless --convert-to pdf --outdir ./output input.docx
```
**Supported**: `.docx`, `.doc`, `.odt`, `.rtf`, `.pptx`, `.ppt`, `.xlsx`, `.xls`, `.ods`, `.csv`, `.html`
**macOS path**: `/Applications/LibreOffice.app/Contents/MacOS/soffice`
**Gotchas:**
- soffice allows only one instance at a time; use `--env:UserInstallation=file:///tmp/libreoffice_tmp` if needed
- Missing Chinese fonts → squares. Ensure Noto Sans SC/Noto Serif SC are installed
- Large files (>50MB) may take 1-2 min; set reasonable timeout
**Priority**: Always prefer soffice for Office→PDF. Fall back to python-pptx/python-docx + HTML + Playwright only if soffice is unavailable.
### Spreadsheet → PDF (without LibreOffice)
Use openpyxl + HTML + Playwright. Columns ≤ 6 → portrait; > 6 → landscape. Scale font inversely with column count.
### §DOCX Pipeline (5-Step with TOC)
For DOCX files that need TOC generation/correction (LibreOffice `--headless` does not recalculate PAGEREF fields).
**When to use**: DOCX has 3+ headings, or mentions "table of contents" / "TOC", or already contains a TOC section. When in doubt, run `toc_validate.py fix-docx` — if it returns `no_toc_needed`, simple conversion is fine.
```bash
# Step 1: Convert to PDF (pass1)
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office input.docx -o pass1.pdf
# Step 2: Clean blank pages
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean pass1.pdf -o pass1_clean.pdf
# Step 3: Fix TOC (adds HYPERLINK + PAGEREF + bookmarks)
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-docx input.docx -o fixed.docx
# Step 4: Correct TOC page numbers using pass1 as reference
python3 "$PDF_SKILL_DIR/scripts/toc_validate.py" fix-pages fixed.docx pass1_clean.pdf -o final.docx
# Step 5: Final convert + clean
python3 "$PDF_SKILL_DIR/scripts/pdf.py" convert.office final.docx -o output.pdf
python3 "$PDF_SKILL_DIR/scripts/pdf.py" pages.clean output.pdf -o output_clean.pdf
```
Check `fix-docx` output `action`: `fixed` → use fixed.docx, `skipped` → use original, `no_toc_needed` → skip to Step 5.
Optional post-validation: `toc_validate.py check-conversion final.docx output_clean.pdf`
---
## §Batch Processing
```python
import os, glob, logging
from pypdf import PdfReader, PdfWriter
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def batch_process(input_dir, operation='merge'):
pdf_files = sorted(glob.glob(os.path.join(input_dir, "*.pdf")))
if operation == 'merge':
writer = PdfWriter()
for f in pdf_files:
try:
for page in PdfReader(f).pages:
writer.add_page(page)
logger.info(f"Processed: {f}")
except Exception as e:
logger.error(f"Failed: {f}: {e}")
with open("merged.pdf", "wb") as out:
writer.write(out)
elif operation == 'extract_text':
for f in pdf_files:
try:
text = "".join(p.extract_text() for p in PdfReader(f).pages)
with open(f.replace('.pdf', '.txt'), 'w') as out:
out.write(text)
except Exception as e:
logger.error(f"Failed: {f}: {e}")
```
---
## Caveats & Performance
| Topic | Detail |
|-------|--------|
| < 50 MB | Instant |
| 50–200 MB | 1–2 minutes |
| > 200 MB | Split first, or extend timeout |
| Memory | ~2-3× input file size |
| Text extraction speed | `pdftotext -bbox-layout` fastest; pdfplumber for structured data |
| Image extraction | `pdfimages` faster than full-page rendering |
| Large PDFs | Process in chunks to manage memory |
## Tooling Inventory
| Library / Tool | Role | Licence |
|----------------|------|---------|
| pikepdf | Low-level PDF manipulation (forms, pages, metadata) | MPL-2.0 |
| pdfplumber | Content extraction (text, tables) | MIT |
| pypdfium2 | Fast rendering, text extraction (PyMuPDF alternative) | Apache/BSD |
| pypdf | Merge, split, crop, metadata, encryption | BSD |
| poppler-utils | CLI text/image extraction, rendering | GPL-2 |
| qpdf | Page manipulation, optimization, encryption, repair | Apache |
| pytesseract | OCR for scanned PDFs | Apache |
| pdf2image | PDF-to-image conversion via poppler | MIT |
| LibreOffice | Office format conversion engine | MPL-2.0 |
+1703
View File
File diff suppressed because it is too large. Load diff
+227
View File
@@ -0,0 +1,227 @@
## Resume / CV Template (ATS-Friendly)
Single-column, clean layout optimised for Applicant Tracking Systems (ATS). No graphics, no sidebars, no colour blocks - just well-structured text that parses perfectly by HR software.
**When to use this template:**
- Applying to corporate jobs through online portals
- Any scenario where the PDF will be machine-parsed before a human reads it
- When the recruiter explicitly asks for "a standard resume"
**When NOT to use (use Academic brief instead):**
- Creative/design industry positions → Academic brief (AltaCV-style)
- Academic CV with publications → Academic brief (Academic CV)
### Resume Design Rules
- **Target 1 page** unless user specifies otherwise
- **Margins**: `left=1.5cm, right=1.5cm, top=1.5cm, bottom=1.5cm`
- **No cover page** - content starts immediately
- **No TOC** - too short for table of contents
- **Font**: FreeSerif (English) or Noto Sans SC (Chinese)
- **Body font size**: 10-10.5pt; Name: 22-26pt; Section titles: 13-14pt
- **⚠️ Minimum font size: 12px (9pt) - HARD FLOOR.** No text in the entire resume may render smaller than 12px. This includes contact info, meta text, footnotes, and captions. Anything below 12px is unreadable in print and fails accessibility checks.
- **Section separator**: thin horizontal rule (`HRFlowable`)
- **Bullet style**: `•` with tight spacing
### Resume Line-Break Rules (Language-Aware)
- **English**: Prefer breaking at word boundaries (spaces, hyphens). If a long word must be split to avoid excessive whitespace, break at a valid syllable boundary and insert a hyphen (`-`) - this is standard typographic practice (e.g., `experi-\nence`, `develop-\nment`). ReportLab supports `wordWrap='CJK'` only for CJK content; for English use default paragraph wrapping with `allowWidows=0, allowOrphans=0`.
- **Chinese/CJK**: Break allowed between any two CJK characters. Never break between a CJK character and its adjacent punctuation (、。,)》 etc. must stay with the preceding character.
- **Mixed content** (e.g., "Python 开发工程师"): Break at CJK boundaries or English word boundaries. Never split an English word in a CJK paragraph unless hyphenated.
- **Contact line**: Email, phone, location separated by `|` or `·`. Each segment must stay on one line - if too long, move to next line at the separator, not mid-segment.
- **Dates and ranges**: "Jan 2022 - Present" must stay as one unit. Never break a date range across lines.
### Resume Page-Fill Rules (Anti-Blank-Space)
- **Goal: Fill ≥85% of the page height.** Content should reach at least the bottom quarter of the page. A resume that stops at 60% height with blank space below = FAIL.
- **Adaptive spacing strategy** (apply in order until page is ≥85% filled):
1. Increase `spaceBefore` / `spaceAfter` on section headers (from 10pt up to 18pt)
2. Increase `leading` (line height) on body text (from 14pt up to 18pt)
3. Increase body `fontSize` by 0.5-1pt (from 10pt up to 11.5pt max)
4. Add a "Professional Summary" or "Key Achievements" section if none exists
5. Increase margins slightly (from 1.5cm up to 2cm) to reduce line width and push content downward
- **Never leave a visible blank area > 3cm at the bottom of the page.**
- **If content overflows to page 2**: do the reverse - reduce spacing, tighten leading (min 12pt), reduce fontSize (min 9pt / 12px), before removing content.
### Complete Resume Template
```python
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, HRFlowable, Table, TableStyle
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.pdfmetrics import registerFontFamily
# ── Font Registration ──
pdfmetrics.registerFont(TTFont('FreeSerif', '/usr/share/fonts/truetype/freefont/FreeSerif.ttf'))
pdfmetrics.registerFont(TTFont('FreeSerif-Bold', '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf'))
pdfmetrics.registerFont(TTFont('FreeSerif-Italic', '/usr/share/fonts/truetype/freefont/FreeSerifItalic.ttf'))
pdfmetrics.registerFont(TTFont('FreeSerif-BoldItalic', '/usr/share/fonts/truetype/freefont/FreeSerifBoldItalic.ttf'))
registerFontFamily('FreeSerif', normal='FreeSerif', bold='FreeSerif-Bold', italic='FreeSerif-Italic', boldItalic='FreeSerif-BoldItalic')
# For Chinese resumes, also register Noto Sans SC:
# pdfmetrics.registerFont(TTFont('Noto Sans SC', '/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf'))
# pdfmetrics.registerFont(TTFont('Noto Sans SC Bold', '/usr/share/fonts/truetype/chinese/NotoSansSC-Bold.ttf'))
# registerFontFamily('Noto Sans SC', normal='Noto Sans SC', bold='Noto Sans SC Bold')
# ── Styles ──
# ACCENT must come from palette.generate (Step 2)
# Run: python3 "$PDF_SKILL_DIR/scripts/pdf.py" palette.generate --title "Resume" --mode minimal
ACCENT = colors.HexColor('<accent from palette>') # Replace with palette output
name_style = ParagraphStyle(
'ResumeName', fontName='FreeSerif', fontSize=24,
leading=28, alignment=TA_CENTER, spaceAfter=2
)
contact_style = ParagraphStyle(
'ResumeContact', fontName='FreeSerif', fontSize=10,
leading=14, alignment=TA_CENTER, textColor=TEXT_MUTED, # From palette --c-muted
spaceAfter=8
)
section_title_style = ParagraphStyle(
'ResumeSectionTitle', fontName='FreeSerif', fontSize=13,
leading=16, spaceBefore=10, spaceAfter=4,
textColor=ACCENT
)
job_title_style = ParagraphStyle(
'ResumeJobTitle', fontName='FreeSerif', fontSize=11,
leading=14, spaceAfter=1
)
job_meta_style = ParagraphStyle(
'ResumeJobMeta', fontName='FreeSerif', fontSize=10,
leading=13, textColor=TEXT_MUTED, spaceAfter=4 # From palette --c-muted
)
bullet_style = ParagraphStyle(
'ResumeBullet', fontName='FreeSerif', fontSize=10,
leading=14, leftIndent=14, bulletIndent=0,
spaceBefore=1, spaceAfter=1
)
body_style = ParagraphStyle(
'ResumeBody', fontName='FreeSerif', fontSize=10,
leading=14, spaceAfter=2
)
# ── Helpers ──
def section_header(title):
"""Section title + thin rule separator."""
return [
Paragraph(f'<b>{title}</b>', section_title_style),
HRFlowable(width='100%', thickness=0.8, color=ACCENT,
spaceBefore=0, spaceAfter=6),
]
def experience_entry(title, company, dates, location, bullets):
"""One work experience block."""
elements = [
Paragraph(f'<b>{title}</b>', job_title_style),
Paragraph(f'{company} | {dates} | {location}', job_meta_style),
]
for b in bullets:
elements.append(Paragraph(f'• {b}', bullet_style))
elements.append(Spacer(1, 4))
return elements
def education_entry(degree, school, dates, details=None):
"""One education block."""
elements = [
Paragraph(f'<b>{degree}</b>', job_title_style),
Paragraph(f'{school} | {dates}', job_meta_style),
]
if details:
elements.append(Paragraph(details, body_style))
elements.append(Spacer(1, 4))
return elements
def skills_row(categories):
"""
Skills as compact label: value pairs.
categories = [('Programming', 'Python, Java, C++'), ('Tools', 'Git, Docker')]
"""
elements = []
for cat, vals in categories:
elements.append(Paragraph(f'<b>{cat}:</b> {vals}', body_style))
return elements
# ── Build Document ──
doc = SimpleDocTemplate(
'resume.pdf', pagesize=A4,
leftMargin=1.5*cm, rightMargin=1.5*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm,
title='Resume - Your Name',
author='Z.ai', creator='Z.ai'
)
story = []
# Header
story.append(Paragraph('<b>YOUR NAME</b>', name_style))
story.append(Paragraph(
'email@example.com | +86 138-0000-0000 | Shanghai, China | github.com/yourname',
contact_style
))
# Summary
story.extend(section_header('PROFESSIONAL SUMMARY'))
story.append(Paragraph(
'Results-driven software engineer with 5+ years of experience in backend systems, '
'distributed computing, and cloud-native architectures. Led a team of 8 engineers '
'delivering a real-time data pipeline processing 2M+ events/sec.',
body_style
))
# Experience
story.extend(section_header('WORK EXPERIENCE'))
story.extend(experience_entry(
'Senior Software Engineer', 'Tech Company Inc.', 'Jan 2022 - Present', 'Shanghai',
[
'Designed and deployed a microservices architecture serving 10M daily active users',
'Reduced API latency by 40% through query optimisation and caching strategies',
'Mentored 3 junior engineers; established code review standards adopted team-wide',
]
))
story.extend(experience_entry(
'Software Engineer', 'Startup Co.', 'Jul 2019 - Dec 2021', 'Beijing',
[
'Built real-time recommendation engine using collaborative filtering (CTR +25%)',
'Implemented CI/CD pipeline reducing deployment time from 2 hours to 15 minutes',
]
))
# Education
story.extend(section_header('EDUCATION'))
story.extend(education_entry(
'M.Sc. Computer Science', 'Tsinghua University', '2017 - 2019',
'GPA: 3.8/4.0 | Thesis: Distributed Graph Processing on Heterogeneous Clusters'
))
story.extend(education_entry(
'B.Eng. Software Engineering', 'Zhejiang University', '2013 - 2017'
))
# Skills
story.extend(section_header('SKILLS'))
story.extend(skills_row([
('Languages', 'Python, Java, Go, SQL, TypeScript'),
('Frameworks', 'Spring Boot, FastAPI, React, Kubernetes, Kafka'),
('Tools', 'Git, Docker, Terraform, AWS (EC2/S3/Lambda), PostgreSQL, Redis'),
]))
doc.build(story)
```
### Resume Checklist
- [ ] **1 page** (unless user says otherwise)
- [ ] **No cover page, no TOC**
- [ ] Tight margins (1.5cm all sides)
- [ ] Name prominent at top (22-26pt)
- [ ] Contact info single line, centered
- [ ] Section headers with consistent separator style
- [ ] Bullets concise - start with action verbs
- [ ] Quantified achievements (%, $, count)
- [ ] No photos, no icons, no colour blocks (ATS-safe)
- [ ] Font: only registered fonts (FreeSerif / Noto Sans SC)
- [ ] **⚠️ Minimum font size 12px (9pt)** - no text smaller than this anywhere
- [ ] **Line breaks are language-aware** - no mid-word English breaks, no CJK punctuation orphans, no date range splits
- [ ] **Page fill ≥85%** - no large blank area at bottom. If sparse, increase spacing/leading/font size adaptively