SnapSparrow

Export View — Implementation Spec

v1.0 · March 24, 2026 · All dimensions at 3000px canvas scale unless noted
0. Reference frame

Canvas scale

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.

Canvas width: 3000px
Outer margins: 160px each side
Inner content width: 2680px
Column count: 12
Column gutter: 64px
Total gutter width: 11 × 64 = 704px
Column unit width: (2680 - 704) / 12 = 164.67px
1. 12-column grid system

1.1 Snap widths

Images occupy fixed column spans. Width = (columns × column_unit) + ((columns - 1) × gutter).

ColumnsWidth (px)Approx %Use
122680100%Full width — hero images
9222383%Primary in 9+3 asymmetric
8176566%Primary in 8+4 asymmetric
6130849%Standard — balanced pairs
485132%Supporting — companion images
362223%Compact — thumbnails, mobile refs

1.2 Valid row compositions

Every row sums to exactly 12 columns. Asymmetric splits have mirrored variants. The algorithm alternates heavy-side placement between rows (see 1.5 Zigzag).

12
Full width
6
6
6 + 6 (even)
8
4
8 + 4
4
8
4 + 8 (mirror)
9
3
9 + 3
3
9
3 + 9 (mirror)
4
4
4
4 + 4 + 4
6
3
3
6 + 3 + 3
3
6
3
3 + 6 + 3
3
3
6
3 + 3 + 6 (mirror)

1.3 Size assignment & priority

Each image receives a priority score. Higher-priority images claim wider column spans.

priority = (annotation_count × 2) + native_area_rank

annotation_count: number of annotations on this image (0–n)
native_area_rank: 1 (smallest) to n (largest) within the set
Column span tiers
TierSpanWidthTriggers
Hero122680px3+ annotations, solo image, or user-pinned
Primary8–91765–2223px2 annotations, or highest priority in an asymmetric row
Standard61308px1 annotation, or default for balanced pairs
Supporting4851px0–1 annotations, paired beside a primary or hero
Compact3622pxRestricted — see eligibility below
3-column eligibility constraint

An image may only occupy a 3-col slot if both conditions are met:

annotations <= 1
AND native_width <= 1000px

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).

1.4 Row-packing algorithm

Greedy, priority-first row composition:

1. Compute priority score for each image.
2. Sort by priority (descending).
3. Take highest-priority unplaced image. Assign its tier's column span.
4. Check remaining columns in current row.
   Find highest-priority unplaced image whose tier fits.
   If a 3-col slot is open, verify candidate meets 3-col eligibility.
5. Repeat step 4 until row = 12 columns or no image fits.
6. If leftover columns remain: expand last-placed image to fill.
7. Apply zigzag (see 1.5) — mirror heavy side if needed.
8. Open new row. Repeat from step 3.

1.5 Zigzag placement

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.

Row 1: 8+4 → heavy LEFT
Row 2: 12 → neutral (no change)
Row 3: 4+8 → heavy RIGHT (mirrored)
Row 4: 6+6 → neutral (no change)
Row 5: 9+3 → heavy LEFT (mirrored back)

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.

1.6 Aspect ratio guardrails

After column assignment, each image scales to its column width preserving native aspect ratio. Two guardrails apply:

GuardrailValueBehavior
Max row height2200pxIf portrait image at assigned width exceeds this, promote to next wider span
Min display scale50%If image would render below 50% of native resolution, warn user (don't silently degrade)
2. Vertical rhythm

2.1 Inter-row gutter formula

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.

h1 = rendered height of row above (including caption if present)
h2 = rendered height of row below
contrast = abs(h1 - h2) / max(h1, h2)

gutter = base + (range × (1 - contrast))

base = 64px  |  range = 128px  |  output: 64–192px

At canvas scale (3000px), 64px base and 128px range. The maximum gutter (192px) equals 3× the column gutter, maintaining proportional harmony with the grid.

2.2 Intra-row gutter

The horizontal gap between side-by-side images within a row is fixed at 64px, matching the column gutter. This is not variable.

3. Title block

Structure

The title block opens the export. Top to bottom:

1. Accent bar + superline (document type · screen count · annotation count)
2. Title (user-authored)
3. Date
4. Hairline rule (full content width)
Accent bar

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.

Superline

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.

Title

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.

Date

Source Sans 3 · 400 weight · 36px · tertiary text color · format: "March 24, 2026" (long month, no leading zero). Margin-top: 16px from title baseline.

Hairline rule

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.

4. Image cards

Card styling

Each screenshot sits on a card with the following properties:

background: white
border: 1px solid, tertiary border color
border-radius: 16px
overflow: hidden (screenshot fills to edges, clipped by radius)
no drop shadow

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.

Annotation badges

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 overlays

Region annotations render as bordered rectangles over the image content. Border: 4px solid, region color. No fill. Border-radius: 8px.

5. Captions

5.1 Placement & scoping

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.

Alignment

Always left-aligned, regardless of which side of the row the image sits on. No exceptions.

Row-bottom alignment

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.

Multi-caption rows

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.

5.2 Gutter absorption

Captions occupy space within the computed inter-row gutter, not in addition to it. This prevents captioned rows from creating double-spacing artifacts.

computed_gutter = aspect-ratio formula output (64–192px)
caption_height = rendered caption text height + 32px top margin

if caption_height < computed_gutter:
  remaining_gap = computed_gutter - caption_height

if caption_height >= computed_gutter:
  remaining_gap = 40px (minimum breathing room)

In multi-caption rows, use the tallest caption's height for gutter math.

5.3 Length guardrails

Narrower columns constrain how much caption text looks good. The engine flags but never truncates — the user decides.

SpanRecommended maxFlag behavior
12 col~120 chars / 2 linesNo flag
8–9 col~100 chars / 2 linesNo flag
6 col~80 chars / 2 linesSoft flag at 3+ lines
4 col~60 chars / 2 linesSoft flag at 3+ lines
3 col~40 chars / 1 lineSoft 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.

6. Annotation manifest

Structure

The manifest appears below all image rows. It lists every annotation in badge-number order.

Section header

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.

Annotation entries

Each entry is a two-column grid: badge on the left, content on the right.

Badge: 64px diameter circle, annotation tool color, white number (600 weight, 32px)
Gap between badge and content: 28px

Metadata line (above body text):
  Type label: Source Sans 3, 600 weight, 28px, uppercase, 0.05em tracking, tertiary color
  Separator: ·
  Image ref: "Image N of M", Source Sans 3, 400 weight, 28px, tertiary color

Body text: Source Sans 3, 400 weight, 36px, primary text color, line-height 1.55

Spacing between entries: 48px
Badge colors

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.

Structure

Hairline rule: 1px, tertiary border color, full content width
Margin-top from last manifest entry: 80px
Margin from rule to footer text: 28px

Left: "COMPOSED WITH SNAPSPARROW"
  Source Sans 3, 400 weight, 28px, letter-spacing 0.06em, uppercase, tertiary color

Right: "snapsparrow.app"
  Source Sans 3, 400 weight, 28px, tertiary color

Date does not repeat in the footer — it already appears in the title block.

8. Color accent system

Coral vertical bar

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.

Bar specifications
LocationWidthHeightRadiusColor
Title block8px72px4px#D85A30
Manifest header8px40px4px#D85A30
9. Typography reference

Font stack

RoleFontSource
Export titleCal SansGoogle Fonts (single weight, 400)
All other export textSource Sans 3Google Fonts (variable, 200–900)
App UI (canvas, toolbar)Source Sans 3Same family — consistent across app and export

Type scale (at 3000px canvas)

ElementFontSizeWeightStyleColor
TitleCal Sans80px400NormalPrimary
Superline (doc type)Source Sans 332px600Uppercase, 0.12emSecondary
Superline (counts)Source Sans 332px500Uppercase, 0.08emTertiary
DateSource Sans 336px400NormalTertiary
CaptionSource Sans 336px400ItalicSecondary
Manifest section headerSource Sans 328px600Uppercase, 0.1emTertiary
Annotation type labelSource Sans 328px600Uppercase, 0.05emTertiary
Annotation image refSource Sans 328px400NormalTertiary
Annotation bodySource Sans 336px400Normal, lh 1.55Primary
Footer textSource Sans 328px400Uppercase, 0.06emTertiary

Spacing reference (at 3000px canvas)

BetweenValue
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 row72px
Last image row to manifest header128px
Between annotation entries48px
Caption top margin (from image edge)32px
Last manifest entry to footer rule80px
Footer rule to footer text28px

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.