All dimensions in this spec are at 3000px canvas width unless explicitly marked (screen). The export renders at canvas scale; screen previews divide by the display ratio.
Images occupy fixed column spans. Width = (columns × column_unit) + ((columns - 1) × gutter).
| Columns | Width (px) | Approx % | Use |
|---|---|---|---|
| 12 | 2680 | 100% | Full width — hero images |
| 9 | 2223 | 83% | Primary in 9+3 asymmetric |
| 8 | 1765 | 66% | Primary in 8+4 asymmetric |
| 6 | 1308 | 49% | Standard — balanced pairs |
| 4 | 851 | 32% | Supporting — companion images |
| 3 | 622 | 23% | Compact — thumbnails, mobile refs |
Every row sums to exactly 12 columns. Asymmetric splits have mirrored variants. The algorithm alternates heavy-side placement between rows (see 1.5 Zigzag).
Each image receives a priority score. Higher-priority images claim wider column spans.
| Tier | Span | Width | Triggers |
|---|---|---|---|
| Hero | 12 | 2680px | 3+ annotations, solo image, or user-pinned |
| Primary | 8–9 | 1765–2223px | 2 annotations, or highest priority in an asymmetric row |
| Standard | 6 | 1308px | 1 annotation, or default for balanced pairs |
| Supporting | 4 | 851px | 0–1 annotations, paired beside a primary or hero |
| Compact | 3 | 622px | Restricted — see eligibility below |
An image may only occupy a 3-col slot if both conditions are met:
If no image qualifies for a 3-col slot in a given row, that composition falls back to the next valid split (e.g., 9+3 becomes 12 or 8+4).
Greedy, priority-first row composition:
For asymmetric rows (8+4, 9+3, 6+3+3, and their mirrors), the algorithm tracks which side the heavier image landed on in the previous asymmetric row and mirrors it for the next. Symmetric rows (12, 6+6, 4+4+4, 3+6+3) are neutral — they do not reset the zigzag counter.
This creates a diagonal reading path. The eye follows the heavier image as it alternates sides, producing the visual rhythm of a magazine spread without manual art direction.
After column assignment, each image scales to its column width preserving native aspect ratio. Two guardrails apply:
| Guardrail | Value | Behavior |
|---|---|---|
| Max row height | 2200px | If portrait image at assigned width exceeds this, promote to next wider span |
| Min display scale | 50% | If image would render below 50% of native resolution, warn user (don't silently degrade) |
The vertical gap between rows scales inversely with height contrast. When adjacent rows have very different heights, the height difference itself creates visual separation — so the gutter shrinks. When rows are similar heights, they risk blurring together — so the gutter expands.
At canvas scale (3000px), 64px base and 128px range. The maximum gutter (192px) equals 3× the column gutter, maintaining proportional harmony with the grid.
The horizontal gap between side-by-side images within a row is fixed at 64px, matching the column gutter. This is not variable.
The title block opens the export. Top to bottom:
A vertical bar, 8px wide × 72px tall, border-radius: 4px, in the brand coral (#D85A30). Positioned to the left of the superline text, vertically centered on the superline's cap height. Gap between bar and text: 24px.
Source Sans 3 · 600 weight · 32px · letter-spacing 0.12em · uppercase · secondary text color. The document type label (e.g., "UX Audit") is 600 weight; the screen/annotation counts are 500 weight, tertiary text color, separated by · with 0.08em letter-spacing.
Cal Sans · 400 weight (the only available weight) · 80px · primary text color · line-height: 1.15. No letter-spacing adjustment — Cal Sans is designed with intentionally tight default spacing.
Source Sans 3 · 400 weight · 36px · tertiary text color · format: "March 24, 2026" (long month, no leading zero). Margin-top: 16px from title baseline.
0.5px (rendered as 1px at 2×) · tertiary border color · full content width (2680px). Margin-top: 40px from date. Margin-bottom: 72px to first image row.
Each screenshot sits on a card with the following properties:
The 16px radius at 3000px canvas renders as roughly 6px at screen scale — tight enough to feel editorial rather than app-like, soft enough to avoid harsh corners.
Badges are positioned within the image card, overlapping the screenshot content. Each badge is a circle, 56px diameter, with a 32px number centered inside, 600 weight, white text on the annotation tool's color. Badge colors match the annotation palette (blue for callout, teal for region, coral for arrow, red for strikethrough). Badges carry an additional scaled palette for sequential same-type annotations.
Region annotations render as bordered rectangles over the image content. Border: 4px solid, region color. No fill. Border-radius: 8px.
Captions are optional, user-authored descriptions of the full image (distinct from annotations, which point at specific elements). A user can add a caption to any image or leave it blank.
Each caption is scoped beneath its own image, left-aligned within that image's column span. In multi-image rows, captions never span the full row width.
Always left-aligned, regardless of which side of the row the image sits on. No exceptions.
Captions attach to their own image's bottom edge, not the row's tallest image. In an asymmetric row where the 8-col image is taller than the 4-col image, the 4-col caption sits immediately below the 4-col image — it does not drop down to the tall image's bottom edge. The caption belongs to its image, not to the row.
When multiple images in a row have captions, each sits within its own column boundaries. They may wrap to different line counts. The inter-row gutter is measured from the bottom of whichever caption (or uncaptioned image) extends furthest.
Captions occupy space within the computed inter-row gutter, not in addition to it. This prevents captioned rows from creating double-spacing artifacts.
In multi-caption rows, use the tallest caption's height for gutter math.
Narrower columns constrain how much caption text looks good. The engine flags but never truncates — the user decides.
| Span | Recommended max | Flag behavior |
|---|---|---|
| 12 col | ~120 chars / 2 lines | No flag |
| 8–9 col | ~100 chars / 2 lines | No flag |
| 6 col | ~80 chars / 2 lines | Soft flag at 3+ lines |
| 4 col | ~60 chars / 2 lines | Soft flag at 3+ lines |
| 3 col | ~40 chars / 1 line | Soft flag at 2+ lines |
"Soft flag" means the in-app canvas shows a subtle indicator that the caption may be too long for its column width. The export engine never truncates.
The manifest appears below all image rows. It lists every annotation in badge-number order.
Accent bar (same coral bar as title block, 8px × 40px) + "ANNOTATIONS" label. Source Sans 3 · 600 weight · 28px · letter-spacing 0.1em · uppercase · tertiary text color. Margin-top from last image row: 128px.
Each entry is a two-column grid: badge on the left, content on the right.
Each badge matches the annotation tool color used in-image. The palette provides scaled variants for sequential same-type annotations (e.g., multiple callouts get distinct but related blues). Colors create a visual link between the manifest entry and the in-image annotation.
Date does not repeat in the footer — it already appears in the title block.
The brand coral (#D85A30) appears as a small vertical bar in two locations: the title block superline and the annotations section header. This bar functions as an editorial section marker — a color moment that signals "this is a structured document, not a screenshot dump."
The bar does not appear anywhere else in the export. Annotation badge colors are the only other color in the composition, and they serve a functional purpose (linking badges to manifest entries), not a decorative one.
| Location | Width | Height | Radius | Color |
|---|---|---|---|---|
| Title block | 8px | 72px | 4px | #D85A30 |
| Manifest header | 8px | 40px | 4px | #D85A30 |
| Role | Font | Source |
|---|---|---|
| Export title | Cal Sans | Google Fonts (single weight, 400) |
| All other export text | Source Sans 3 | Google Fonts (variable, 200–900) |
| App UI (canvas, toolbar) | Source Sans 3 | Same family — consistent across app and export |
| Element | Font | Size | Weight | Style | Color |
|---|---|---|---|---|---|
| Title | Cal Sans | 80px | 400 | Normal | Primary |
| Superline (doc type) | Source Sans 3 | 32px | 600 | Uppercase, 0.12em | Secondary |
| Superline (counts) | Source Sans 3 | 32px | 500 | Uppercase, 0.08em | Tertiary |
| Date | Source Sans 3 | 36px | 400 | Normal | Tertiary |
| Caption | Source Sans 3 | 36px | 400 | Italic | Secondary |
| Manifest section header | Source Sans 3 | 28px | 600 | Uppercase, 0.1em | Tertiary |
| Annotation type label | Source Sans 3 | 28px | 600 | Uppercase, 0.05em | Tertiary |
| Annotation image ref | Source Sans 3 | 28px | 400 | Normal | Tertiary |
| Annotation body | Source Sans 3 | 36px | 400 | Normal, lh 1.55 | Primary |
| Footer text | Source Sans 3 | 28px | 400 | Uppercase, 0.06em | Tertiary |
| Between | Value |
|---|---|
| Canvas edge to content (outer margin) | 160px |
| Column gutter (horizontal, fixed) | 64px |
| Inter-row gutter (vertical, variable) | 64–192px |
| Title block rule to first image row | 72px |
| Last image row to manifest header | 128px |
| Between annotation entries | 48px |
| Caption top margin (from image edge) | 32px |
| Last manifest entry to footer rule | 80px |
| Footer rule to footer text | 28px |
End of spec. Questions flagged for real-world validation: scale floor value (50% vs lower), max export width behavior at canvas edge, badge numbering across paginated exports, portrait/extreme-portrait aspect ratio cutoff threshold, and 3+6+3 composition validation with real content.