# Business Card Template Specification — v1.0

This document defines the contract between the **Card Generator** and a **template file**, so a
designer app (e.g. Claude Design) can produce a template that the generator can consume without
any code changes.

The template is a **fixed-size SVG canvas with named slots**. The generator fills the slots with
contact data, the logo mark, and QR codes. Geometry lives in the template; data lives in the record.

---

## 1. Production geometry

| Property | Front | Back | Notes |
| --- | --- | --- | --- |
| Canvas (px) | 1000 × 600 | 1000 × 600 | Digitized front uses 1000 × 606 |
| Final trim | 90 × 54 mm | 90 × 54 mm | standard business card |
| Bleed | 3 mm each side | 3 mm each side | print page = 96 × 60 mm |
| Crop marks | generated by app | generated by app | do not draw crop marks in the template |

Templates are **fixed-canvas**: elements are absolutely positioned or placed in a fixed grid. The
generator does not reflow layout for long values — it shrinks text in marked slots (see §7).

---

## 2. Canonical data record

The generator interpolates this JSON record into the template:

```json
{
  "fullName": "John Gao",
  "title": "Head of International Markets",
  "company": "Heyuan Magnetics Limited",
  "legalName": "Ningbo Heyuan Magnetics Limited",
  "phones": ["+86 180 6717 7036", "+49 152 0394 3990"],
  "email": "john.gao@hymagnetics.com",
  "website": "www.hymagnetics.com",
  "address": "Shijiumiao Industrial Zone, Ningbo, China 315157",
  "photo": null
}
```

| Field | Type | Rules |
| --- | --- | --- |
| `fullName` | string | required, one line |
| `title` | string | required, one line |
| `company` | string | required |
| `legalName` | string | optional, back only |
| `phones` | string[] | 0–2 items |
| `email` | string | optional |
| `website` | string | optional, display form without scheme |
| `address` | string | optional, wraps to max 2 lines |
| `photo` | string\|null | optional data URL, back "keeper" template only |

---

## 3. Template manifest

Every template declares a small manifest so the generator and designer app agree on what it is.
In an SVG file the manifest is an XML comment near the top:

```html
<!-- @template-manifest
{
  "schemaVersion": "1.0",
  "id": "1c-blueprint",
  "name": "1c Blueprint",
  "side": "front",
  "canvas": { "width": 1000, "height": 600 },
  "bleedMm": 3,
  "fonts": ["Barlow Condensed", "Barlow"],
  "placeholders": ["logo", "company", "fullName", "title", "phones", "email", "website", "address", "vcardQr"]
}
-->
```

### Manifest schema

```json
{
  "schemaVersion": "1.0",
  "id": "string, unique, kebab-case",
  "name": "string, human readable",
  "side": "front | back",
  "canvas": { "width": 1000, "height": 600 },
  "bleedMm": 3,
  "fonts": ["Barlow Condensed", "Barlow"],
  "placeholders": ["fullName", "title", "company", "phones", "email", "website", "address", "logo", "vcardQr", "websiteQr"]
}
```

- `side` must be `front` or `back`.
- `canvas` must be `1000 × 600`, except the Digitized front which may be `1000 × 606`.
- `placeholders` is informational; the generator also detects slots directly from the body.

---

## 4. Slot syntax

Templates use `{{slot}}` tokens. Text nodes are replaced; the surrounding SVG attributes (position,
font family, size, weight, fill) are preserved.

### Direct fields

| Token | Value |
| --- | --- |
| `{{fullName}}` | `John Gao` |
| `{{title}}` | `Head of International Markets` |
| `{{company}}` | `Heyuan Magnetics Limited` |
| `{{legalName}}` | `Ningbo Heyuan Magnetics Limited` |
| `{{email}}` | `john.gao@hymagnetics.com` |
| `{{website}}` | `www.hymagnetics.com` |
| `{{address}}` | full address, single line |

### Array access and helpers

| Token | Value |
| --- | --- |
| `{{phones[0]}}` | first phone or empty |
| `{{phones[1]}}` | second phone or empty |
| `{{phones.joined}}` | phones joined with ` · ` |

### Derived / structured values

| Token | Value |
| --- | --- |
| `{{company.line1}}` | first word of company |
| `{{company.line2}}` | remaining words of company |
| `{{address.line1}}` | address wrapped, first line |
| `{{address.line2}}` | address wrapped, second line (may be empty) |
| `{{initials}}` | initials derived from fullName, e.g. `JG` |
| `{{website.url}}` | `https://www.hymagnetics.com` |

### Transforms

Append `|upper` or `|lower`:

```text
{{title|upper}}   ->  HEAD OF INTERNATIONAL MARKETS
```

### Logo mark

`{{logo}}` inserts the standard 7-rectangle pixel-grid logo (viewBox `0 0 32 32`).

```svg
<g fill="{{markColor}}" shape-rendering="crispEdges">
  <rect x="0" y="0" width="10" height="10"/>
  <rect x="15" y="0" width="11" height="7"/>
  <rect x="26" y="0" width="6" height="32"/>
  <rect x="1" y="26" width="31" height="6"/>
  <rect x="1" y="14" width="6" height="12"/>
  <rect x="15" y="11" width="8" height="3"/>
  <rect x="10" y="14" width="13" height="8"/>
</g>
```

`{{markColor}}` defaults to brand red `#b93c0e`; use `#d24a1f` on dark grounds.

### QR slots

| Token | Value |
| --- | --- |
| `{{vcardQr}}` | the generated vCard QR (scan-to-save-contact), styled by the app's QR settings |
| `{{websiteQr}}` | the generated website QR |

The template must reserve a **square** area and place a **white plate** behind the QR for
scannability. The generator inserts the QR SVG at that location.

---

## 5. Design tokens

Use these exact color tokens so the template stays on-system:

| Token | Value |
| --- | --- |
| `--color-bg` | `#f2f2f3` |
| `--color-text` | `#1d1f20` |
| `--color-accent` | `#5980a6` |
| `--color-accent-700` | `#416180` |
| `--color-accent-900` | `#1d2d3d` |
| `--color-accent-300` | `#b5d9fd` |
| brand red | `#b93c0e` |
| brand red (dark ground) | `#d24a1f` |
| neutral-500 | `#98989b` |
| neutral-600 | `#7a7a7d` |

## 6. Fonts

| Role | Family | Weights |
| --- | --- | --- |
| Headings / labels | Barlow Condensed | 400, 600 |
| Body / values | Barlow | 400, 500, 700 |
| Digitized body | Arimo | 400, 700 |
| Digitized name | Tinos | 400, 700 |

Barlow and Barlow Condensed are bundled locally. Arimo and Tinos fall back to system fonts if
offline.

---

## 7. Text fitting

Mark a text element with `data-fit="<maxWidth>"` (in the 1000px canvas coordinate space). The
generator measures the rendered text and reduces `font-size` until it fits (minimum 7px).

```svg
<text x="70" y="430" font-size="34" data-fit="300">{{fullName}}</text>
```

Recommended max widths for the three-column titleblock (1000px canvas):

- name: 300
- phone / email / website / address values: 220–300

---

## 8. Validation checklist

A template is valid when:

- [ ] manifest `id` is unique and kebab-case
- [ ] `side` is `front` or `back`
- [ ] canvas is `1000 × 600` (or `1000 × 606` for the Digitized front)
- [ ] all `{{slots}}` reference known fields/derived tokens
- [ ] any `{{qr}}` slot sits on a white plate in a square region
- [ ] no crop marks, bleed marks, or page borders are drawn inside the template
- [ ] text slots that may overflow have `data-fit`

---

## 9. Starter template

A ready-to-edit example is provided at:

- `templates/blueprint-front.svg` — the 1c Blueprint front with every slot filled.

Download it, open it in the designer app, modify the geometry/style, keep the `{{slot}}` tokens,
and hand the modified SVG back to the generator.

---

## 10. Workflow with a designer app

1. Feed this document to Claude Design as the system/instruction context.
2. Give it the data record (§2) and ask it to produce one template per side.
3. Validate each output against §8.
4. Import the returned SVG files into the Card Generator.
